Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import warnings
import numbers
import multiprocessing as mp
from collections import OrderedDict
from collections import OrderedDict, defaultdict
import numpy as np
from qiskit.exceptions import QiskitError
from qiskit.util import is_main_process
Expand Down Expand Up @@ -164,6 +164,7 @@ def __init__(self, *regs, name=None, global_phase=0):
self._qubits = []
self._clbits = []
self._ancillas = []
self._calibrations = defaultdict(dict)
self.add_register(*regs)

# Parameter table tracks instructions with variable parameters.
Expand All @@ -186,6 +187,15 @@ def data(self):
"""
return QuantumCircuitData(self)

@property
def calibrations(self):
"""Return calibration dictionary.

The custom pulse definition of a given gate is of the form
{'gate_name': {(qubits, params): schedule}}
"""
return dict(self._calibrations)

@data.setter
def data(self, data_input):
"""Sets the circuit data from a list of instructions and context.
Expand Down Expand Up @@ -2253,6 +2263,23 @@ def cz(self, control_qubit, target_qubit, # pylint: disable=invalid-name
return self.append(CZGate(label=label, ctrl_state=ctrl_state),
[control_qubit, target_qubit], [])

def add_calibration(self, gate, qubits, schedule, params=None):
"""Register a low-level, custom pulse definition for the given gate.

Args:
gate (Union[Gate, str]): Gate information.
Comment thread
SooluThomas marked this conversation as resolved.
qubits (Union[int, Tuple[int]]): List of qubits to be measured.
schedule (Schedule): Schedule information.
params (Optional[List[Union[float, Parameter]]]): A list of parameters.

Raises:
Exception: if the gate is of type string and params is None.
"""
if isinstance(gate, Gate):
self._calibrations[gate.name][(tuple(qubits), tuple(gate.params))] = schedule
Comment thread
lcapelluto marked this conversation as resolved.
else:
self._calibrations[gate][(tuple(qubits), tuple(params))] = schedule


def _circuit_from_qasm(qasm):
# pylint: disable=cyclic-import
Expand Down
10 changes: 10 additions & 0 deletions qiskit/compiler/assemble.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ def assemble(experiments: Union[QuantumCircuit, List[QuantumCircuit], Schedule,

Raises:
QiskitError: if the input cannot be interpreted as either circuits or schedules
NotImplementedError: if circuit.calibrations is not empty.
"""
start_time = time()
experiments = experiments if isinstance(experiments, list) else [experiments]
Expand All @@ -141,6 +142,15 @@ def assemble(experiments: Union[QuantumCircuit, List[QuantumCircuit], Schedule,

# assemble either circuits or schedules
if all(isinstance(exp, QuantumCircuit) for exp in experiments):
# calibrate circuits to schedules (if any)
for exp in experiments:
if len(exp.calibrations) != 0:
# TODO: Do something here to schedule the circuits
# Raise an error - NotImplementedError" or try to schedule by adding
# cals to inst_map and call schedule.
# if scheduled, raise a warning - "Let the user know whats happening"
raise NotImplementedError

run_config = _parse_circuit_args(parameter_binds, **run_config_common_dict)

# If circuits are parameterized, bind parameters and remove from run_config
Expand Down
5 changes: 5 additions & 0 deletions qiskit/compiler/transpile.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,11 @@ def callback_func(**kwargs):
_log_transpile_time(start_time, end_time)
return circuits

for circuit in circuits:
if len(circuit.calibrations) != 0:
warnings.warn("Transpiling with calibrations are not supported currently.",
UserWarning)

if pass_manager is not None:
_check_conflicting_argument(optimization_level=optimization_level, basis_gates=basis_gates,
coupling_map=coupling_map, seed_transpiler=seed_transpiler,
Expand Down
36 changes: 36 additions & 0 deletions releasenotes/notes/pulse-gate-calibrations-78fd3fa5a5328761.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
features:
- |
Circuits now support a new feature: Pulse gates. This feature enables users who are working
primarily with ``QuantumCircuits`` to override (for basis gates) or specify (for standard
and custom gates) a definition of a ``Gate`` operation in terms of time-ordered signals
across hardware channels. In other words, it allow users to provide a pulse-level "custom
gate calibration."

The circuits are built exactly as before. For example::

from qiskit import pulse
from qiskit.circuit import QuantumCircuit, Gate

class RxGate(Gate):
def __init__(self, theta):
super().__init__('rxtheta', 1, [theta])

circ = QuantumCircuit(1)
circ.h(0)
circ.append(RxGate(3.14), [1])

Then, the user must register the calibration with the gate via the circuit method
``add_calibration``, which takes a schedule definition as well as the specific qubits
and parameters that it is defined for::

# Define the gate implementation as a schedule
with pulse.build() as custom_h_schedule:
pulse.play(pulse.library.Drag(...), pulse.DriveChannel(0))

with pulse.build() as q1_x180:
pulse.play(pulse.library.Gaussian(...), pulse.DriveChannel(1))

# Register the schedule to the gate
circ.add_calibration('h', [0], custom_h_schedule) # or gate.name string to register
circ.add_calibration(RxGate(3.14), [0], q1_x180) # Can accept gate
39 changes: 38 additions & 1 deletion test/python/circuit/test_circuit_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@

import unittest
import numpy as np
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit, pulse
from qiskit.circuit.library import RXGate, RYGate
from qiskit.test import QiskitTestCase
from qiskit.circuit.exceptions import CircuitError
# pylint: disable=unused-import
Expand Down Expand Up @@ -614,6 +615,42 @@ def test_num_qubits_multiple_register_circuit(self):
circ = QuantumCircuit(q_reg1, q_reg2, q_reg3)
self.assertEqual(circ.num_qubits, 18)

def test_calibrations_basis_gates(self):
"""Check if the calibrations for basis gates provided are added correctly."""
circ = QuantumCircuit(2)

with pulse.build() as q0_x180:
pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0))
with pulse.build() as q1_y90:
pulse.play(pulse.library.Gaussian(20, -1.0, 3.0), pulse.DriveChannel(1))

# Add calibration
circ.add_calibration(RXGate(3.14), [0], q0_x180)
circ.add_calibration(RYGate(1.57), [1], q1_y90)

self.assertEqual(set(circ.calibrations.keys()), {'rx', 'ry'})
self.assertEqual(set(circ.calibrations['rx'].keys()), {((0,), (3.14,))})
self.assertEqual(set(circ.calibrations['ry'].keys()), {((1,), (1.57,))})
self.assertEqual(circ.calibrations['rx'][((0,), (3.14,))].instructions,
q0_x180.instructions)
self.assertEqual(circ.calibrations['ry'][((1,), (1.57,))].instructions,
q1_y90.instructions)

Comment thread
lcapelluto marked this conversation as resolved.
def test_calibrations_custom_gates(self):
"""Check if the calibrations for custom gates with params provided are added correctly."""
circ = QuantumCircuit(3)

with pulse.build() as q0_x180:
pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0))

# Add calibrations with a custom gate 'rxt'
circ.add_calibration('rxt', [0], q0_x180, params=[1.57, 3.14, 4.71])

self.assertEqual(set(circ.calibrations.keys()), {'rxt'})
self.assertEqual(set(circ.calibrations['rxt'].keys()), {((0,), (1.57, 3.14, 4.71))})
self.assertEqual(circ.calibrations['rxt'][((0,), (1.57, 3.14, 4.71))].instructions,
q0_x180.instructions)


if __name__ == '__main__':
unittest.main()