Skip to content

Commit

Permalink
Merge main
Browse files Browse the repository at this point in the history
  • Loading branch information
stavros11 committed Dec 15, 2023
2 parents 186729a + c6f41c2 commit e80d560
Show file tree
Hide file tree
Showing 72 changed files with 4,122 additions and 1,707 deletions.
10 changes: 7 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,17 @@ repos:
rev: 23.11.0
hooks:
- id: black
args:
- --line-length=120
- repo: https://github.com/pycqa/isort
rev: 5.12.0
rev: 5.13.1
hooks:
- id: isort
args: ["--profile", "black"]
- repo: https://github.com/PyCQA/docformatter
rev: v1.7.1
hooks:
- id: docformatter
additional_dependencies: [tomli]
args: [--in-place, --config, ./pyproject.toml]
- repo: https://github.com/asottile/pyupgrade
rev: v3.15.0
hooks:
Expand Down
4 changes: 3 additions & 1 deletion examples/fidelity_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
platform.start()
# Executes a pulse sequence.
results = platform.measure_fidelity(qubits=[1, 2, 3, 4], nshots=3000)
print(f"results[qubit] (rotation_angle, threshold, fidelity, assignment_fidelity): {results}")
print(
f"results[qubit] (rotation_angle, threshold, fidelity, assignment_fidelity): {results}"
)
# Turn off lab instruments
platform.stop()
# Disconnect from the instruments
Expand Down
511 changes: 258 additions & 253 deletions poetry.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pyvisa-py = { version = "0.5.3", optional = true }
qm-qua = { version = "==1.1.1", optional = true }
qualang-tools = { version = "==0.14.0", optional = true}
setuptools = { version = ">67.0.0", optional = true }
laboneq = { version = "==2.20.1", optional = true }
laboneq = { version = "==2.21.0", optional = true }
qibosoq = { version = ">=0.0.4,<0.2", optional = true }

[tool.poetry.group.docs]
Expand All @@ -55,7 +55,7 @@ qcodes = "^0.37.0"
qcodes_contrib_drivers = "0.18.0"
qibosoq = ">=0.0.4,<0.2"
qualang-tools = "==0.14.0"
laboneq = "==2.20.1"
laboneq = "==2.21.0"

[tool.poetry.group.tests]
optional = true
Expand Down
22 changes: 16 additions & 6 deletions src/qibolab/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ def apply_gate_density_matrix(self, gate, state, nqubits): # pragma: no cover
def transpile(self, circuit):
"""Applies the transpiler to a single circuit.
This transforms the circuit into proper connectivity and native gates.
This transforms the circuit into proper connectivity and native
gates.
"""
# TODO: Move this method to transpilers
if self.transpiler is None or self.transpiler.is_satisfied(circuit):
Expand All @@ -51,7 +52,8 @@ def transpile(self, circuit):
return native_circuit, qubit_map

def assign_measurements(self, measurement_map, readout):
"""Assigning measurement outcomes to :class:`qibo.states.MeasurementResult` for each gate.
"""Assigning measurement outcomes to
:class:`qibo.states.MeasurementResult` for each gate.
This allows properly obtaining the measured shots from the :class:`qibolab.pulses.ReadoutPulse` object obtaned after pulse sequence execution.
Expand Down Expand Up @@ -107,7 +109,8 @@ def execute_circuit(self, circuit, initial_state=None, nshots=1000):
return result

def execute_circuits(self, circuits, initial_state=None, nshots=1000):
"""Executes multiple quantum circuits with a single communication with the control electronics.
"""Executes multiple quantum circuits with a single communication with
the control electronics.
Circuits are unrolled to a single pulse sequence.
Expand All @@ -134,7 +137,10 @@ def execute_circuits(self, circuits, initial_state=None, nshots=1000):
# TODO: Maybe these loops can be parallelized
native_circuits, _ = zip(*(self.transpile(circuit) for circuit in circuits))
sequences, measurement_maps = zip(
*(self.compiler.compile(circuit, self.platform) for circuit in native_circuits)
*(
self.compiler.compile(circuit, self.platform)
for circuit in native_circuits
)
)

if not self.platform.is_connected:
Expand All @@ -150,9 +156,13 @@ def execute_circuits(self, circuits, initial_state=None, nshots=1000):
results = []
readout = {k: deque(v) for k, v in readout.items()}
for circuit, measurement_map in zip(circuits, measurement_maps):
results.append(MeasurementOutcomes(circuit.measurements, self, nshots=nshots))
results.append(
MeasurementOutcomes(circuit.measurements, self, nshots=nshots)
)
for gate, sequence in measurement_map.items():
samples = [readout[pulse.serial].popleft().samples for pulse in sequence.pulses]
samples = [
readout[pulse.serial].popleft().samples for pulse in sequence.pulses
]
gate.result.backend = self
gate.result.register_samples(np.array(samples).T)
return results
40 changes: 24 additions & 16 deletions src/qibolab/channels.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@
def check_max_offset(offset, max_offset):
"""Checks if a given offset value exceeds the maximum supported offset.
This is to avoid sending high currents that could damage lab equipment
such as amplifiers.
This is to avoid sending high currents that could damage lab
equipment such as amplifiers.
"""
if max_offset is not None and abs(offset) > max_offset:
raise_error(ValueError, f"{offset} exceeds the maximum allowed offset {max_offset}.")
raise_error(
ValueError, f"{offset} exceeds the maximum allowed offset {max_offset}."
)


@dataclass
Expand All @@ -26,18 +28,19 @@ class Channel:
port: Optional[Port] = None
"""Instrument port that is connected to this channel."""
local_oscillator: Optional[LocalOscillator] = None
"""Instrument object controlling the local oscillator connected to this channel.
"""Instrument object controlling the local oscillator connected to this
channel.
Not applicable for setups that do not use external local oscillators because the
controller can send sufficiently high frequencies or contains internal local
oscillators.
Not applicable for setups that do not use external local oscillators
because the controller can send sufficiently high frequencies or
contains internal local oscillators.
"""
max_offset: Optional[float] = None
"""Maximum DC voltage that we can safely send through this channel.
Sending high voltages for prolonged times may damage amplifiers or other lab equipment.
If the user attempts to send a higher value an error will be raised to prevent
execution in real instruments.
Sending high voltages for prolonged times may damage amplifiers or
other lab equipment. If the user attempts to send a higher value an
error will be raised to prevent execution in real instruments.
"""

@property
Expand Down Expand Up @@ -112,21 +115,24 @@ def filters(self, value):

@dataclass
class ChannelMap:
"""Collection of :class:`qibolab.designs.channel.Channel` objects identified by name.
"""Collection of :class:`qibolab.designs.channel.Channel` objects
identified by name.
Essentially, it allows creating a mapping of names to channels just
specifying the names.
"""

_channels: Dict[str, Channel] = field(default_factory=dict)

def add(self, *items):
"""Add multiple items to the channel map.
If :class:`qibolab.channels.Channel` objects are given they are added to the channel map.
If a different type is given, a :class:`qibolab.channels.Channel` with the
corresponding name is created and added to the channel map.
If
:class: `qibolab.channels.Channel` objects are given they are
added to the channel map. If a different type is
given, a
:class: `qibolab.channels.Channel` with the corresponding name
is created and added to the channel map.
"""
for item in items:
if isinstance(item, Channel):
Expand All @@ -140,7 +146,9 @@ def __getitem__(self, name):

def __setitem__(self, name, channel):
if not isinstance(channel, Channel):
raise_error(TypeError, f"Cannot add channel of type {type(channel)} to ChannelMap.")
raise_error(
TypeError, f"Cannot add channel of type {type(channel)} to ChannelMap."
)
self._channels[name] = channel

def __contains__(self, name):
Expand Down
20 changes: 16 additions & 4 deletions src/qibolab/compilers/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@

@dataclass
class Compiler:
"""Compiler that transforms a :class:`qibo.models.Circuit` to a :class:`qibolab.pulses.PulseSequence`.
"""Compiler that transforms a :class:`qibo.models.Circuit` to a
:class:`qibolab.pulses.PulseSequence`.
The transformation is done using a dictionary of rules which map each Qibo gate to a
pulse sequence and some virtual Z-phases.
Expand Down Expand Up @@ -72,7 +73,10 @@ def __delitem__(self, item):
try:
del self.rules[item]
except KeyError:
raise_error(KeyError, f"Cannot remove {item} from compiler because it does not exist.")
raise_error(
KeyError,
f"Cannot remove {item} from compiler because it does not exist.",
)

def register(self, gate_cls):
"""Decorator for registering a function as a rule in the compiler.
Expand All @@ -90,7 +94,9 @@ def inner(func):

return inner

def _compile_gate(self, gate, platform, sequence, virtual_z_phases, moment_start, delays):
def _compile_gate(
self, gate, platform, sequence, virtual_z_phases, moment_start, delays
):
"""Adds a single gate to the pulse sequence."""
rule = self[gate.__class__]
# get local sequence and phases for the current gate
Expand All @@ -99,7 +105,13 @@ def _compile_gate(self, gate, platform, sequence, virtual_z_phases, moment_start
# update global pulse sequence
# determine the right start time based on the availability of the qubits involved
all_qubits = {*gate_sequence.qubits, *gate.qubits}
start = max(*[sequence.get_qubit_pulses(qubit).finish + delays[qubit] for qubit in all_qubits], moment_start)
start = max(
*[
sequence.get_qubit_pulses(qubit).finish + delays[qubit]
for qubit in all_qubits
],
moment_start,
)
# shift start time and phase according to the global sequence
for pulse in gate_sequence:
pulse.start += start
Expand Down
12 changes: 8 additions & 4 deletions src/qibolab/compilers/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,18 @@ def u3_rule(gate, platform):
virtual_z_phases = {qubit: lam}
sequence = PulseSequence()
# Fetch pi/2 pulse from calibration
RX90_pulse_1 = platform.create_RX90_pulse(qubit, start=0, relative_phase=virtual_z_phases[qubit])
RX90_pulse_1 = platform.create_RX90_pulse(
qubit, start=0, relative_phase=virtual_z_phases[qubit]
)
# apply RX(pi/2)
sequence.add(RX90_pulse_1)
# apply RZ(theta)
virtual_z_phases[qubit] += theta
# Fetch pi/2 pulse from calibration
RX90_pulse_2 = platform.create_RX90_pulse(
qubit, start=RX90_pulse_1.finish, relative_phase=virtual_z_phases[qubit] - math.pi
qubit,
start=RX90_pulse_1.finish,
relative_phase=virtual_z_phases[qubit] - math.pi,
)
# apply RX(-pi/2)
sequence.add(RX90_pulse_2)
Expand All @@ -63,8 +67,8 @@ def u3_rule(gate, platform):
def cz_rule(gate, platform):
"""CZ applied as defined in the platform runcard.
Applying the CZ gate may involve sending pulses on qubits
that the gate is not directly acting on.
Applying the CZ gate may involve sending pulses on qubits that the
gate is not directly acting on.
"""
return platform.create_CZ_pulse_sequence(gate.qubits)

Expand Down
5 changes: 3 additions & 2 deletions src/qibolab/couplers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@
class Coupler:
"""Representation of a physical coupler.
Coupler objects are instantiated by :class:`qibolab.platforms.platform.Platform`
but they are passed to instrument designs in order to play pulses.
Coupler objects are instantiated by
:class: `qibolab.platforms.platform.Platform` but they are
passed to instrument designs in order to play pulses.
"""

name: QubitId
Expand Down
21 changes: 17 additions & 4 deletions src/qibolab/dummy.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@


def remove_couplers(runcard):
"""Remove coupler sections from runcard to create a dummy platform without couplers."""
"""Remove coupler sections from runcard to create a dummy platform without
couplers."""
runcard["topology"] = list(runcard["topology"].values())
del runcard["couplers"]
del runcard["native_gates"]["coupler"]
Expand Down Expand Up @@ -43,8 +44,12 @@ def create_dummy(with_couplers: bool = True):
nqubits = runcard["nqubits"]
channels = ChannelMap()
channels |= Channel("readout", port=instrument["readout"])
channels |= (Channel(f"drive-{i}", port=instrument[f"drive-{i}"]) for i in range(nqubits))
channels |= (Channel(f"flux-{i}", port=instrument[f"flux-{i}"]) for i in range(nqubits))
channels |= (
Channel(f"drive-{i}", port=instrument[f"drive-{i}"]) for i in range(nqubits)
)
channels |= (
Channel(f"flux-{i}", port=instrument[f"flux-{i}"]) for i in range(nqubits)
)
channels |= Channel("twpa", port=None)
if with_couplers:
channels |= (
Expand All @@ -71,4 +76,12 @@ def create_dummy(with_couplers: bool = True):

instruments = {instrument.name: instrument, twpa_pump.name: twpa_pump}
name = "dummy_couplers" if with_couplers else "dummy"
return Platform(name, qubits, pairs, instruments, settings, resonator_type="2D", couplers=couplers)
return Platform(
name,
qubits,
pairs,
instruments,
settings,
resonator_type="2D",
couplers=couplers,
)
38 changes: 23 additions & 15 deletions src/qibolab/execution_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,26 @@


class AcquisitionType(Enum):
"""Data acquisition from hardware"""
"""Data acquisition from hardware."""

DISCRIMINATION = auto()
"""Demodulate, integrate the waveform and discriminate among states based on the voltages"""
"""Demodulate, integrate the waveform and discriminate among states based
on the voltages."""
INTEGRATION = auto()
"""Demodulate and integrate the waveform"""
"""Demodulate and integrate the waveform."""
RAW = auto()
"""Acquire the waveform as it is"""
"""Acquire the waveform as it is."""
SPECTROSCOPY = auto()
"""Zurich Integration mode for RO frequency sweeps"""
"""Zurich Integration mode for RO frequency sweeps."""


class AveragingMode(Enum):
"""Data averaging modes from hardware"""
"""Data averaging modes from hardware."""

CYCLIC = auto()
"""Better averaging for short timescale noise"""
"""Better averaging for short timescale noise."""
SINGLESHOT = auto()
"""SINGLESHOT: No averaging"""
"""SINGLESHOT: No averaging."""
SEQUENTIAL = auto()
"""SEQUENTIAL: Worse averaging for noise[Avoid]"""

Expand All @@ -52,20 +53,27 @@ class AveragingMode(Enum):

@dataclass(frozen=True)
class ExecutionParameters:
"""Data structure to deal with execution parameters"""
"""Data structure to deal with execution parameters."""

nshots: Optional[int] = None
"""Number of shots to sample from the experiment. Default is the runcard value."""
"""Number of shots to sample from the experiment.
Default is the runcard value.
"""
relaxation_time: Optional[int] = None
"""Time to wait for the qubit to relax to its ground Sample between shots in ns. Default is the runcard value."""
"""Time to wait for the qubit to relax to its ground Sample between shots
in ns.
Default is the runcard value.
"""
fast_reset: bool = False
"""Enable or disable fast reset"""
"""Enable or disable fast reset."""
acquisition_type: AcquisitionType = AcquisitionType.DISCRIMINATION
"""Data acquisition type"""
"""Data acquisition type."""
averaging_mode: AveragingMode = AveragingMode.SINGLESHOT
"""Data averaging mode"""
"""Data averaging mode."""

@property
def results_type(self):
"""Returns corresponding results class"""
"""Returns corresponding results class."""
return RESULTS_TYPE[self.averaging_mode][self.acquisition_type]
Loading

0 comments on commit e80d560

Please sign in to comment.