From b1d8233782133600e78e10d7785e2461f689ec16 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Sun, 28 Nov 2021 17:24:47 +0100 Subject: [PATCH 01/11] * First draft of the instruction duration odification. --- qiskit/compiler/transpiler.py | 4 +- qiskit/transpiler/instruction_durations.py | 51 +++++++++++++------ test/python/circuit/test_scheduled_circuit.py | 16 ++++-- .../transpiler/test_dynamical_decoupling.py | 20 ++++---- .../transpiler/test_instruction_alignments.py | 18 +++---- .../transpiler/test_instruction_durations.py | 6 +-- .../python/transpiler/test_scheduling_pass.py | 12 ++--- 7 files changed, 78 insertions(+), 49 deletions(-) diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index c3ace64350ab..26375fffe07a 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -831,8 +831,8 @@ def _parse_instruction_durations(backend, inst_durations, dt, circuits): if circ.calibrations: cal_durations = [] for gate, gate_cals in circ.calibrations.items(): - for (qubits, _), schedule in gate_cals.items(): - cal_durations.append((gate, qubits, schedule.duration)) + for (qubits, parameters), schedule in gate_cals.items(): + cal_durations.append((gate, qubits, parameters, schedule.duration)) circ_durations.update(cal_durations, circ_durations.dt) if inst_durations: diff --git a/qiskit/transpiler/instruction_durations.py b/qiskit/transpiler/instruction_durations.py index 423eb5146ea2..a37275f61a59 100644 --- a/qiskit/transpiler/instruction_durations.py +++ b/qiskit/transpiler/instruction_durations.py @@ -35,6 +35,7 @@ def __init__( ): self.duration_by_name = {} self.duration_by_name_qubits = {} + self.duration_by_name_qubits_params = {} self.dt = dt if instruction_durations: self.update(instruction_durations) @@ -108,25 +109,29 @@ def update(self, inst_durations: Optional["InstructionDurationsType"], dt: float if isinstance(inst_durations, InstructionDurations): self.duration_by_name.update(inst_durations.duration_by_name) self.duration_by_name_qubits.update(inst_durations.duration_by_name_qubits) + self.duration_by_name_qubits_params.update(inst_durations.duration_by_name_qubits_params) else: for i, items in enumerate(inst_durations): if len(items) == 3: inst_durations[i] = (*items, "dt") # set default unit - elif len(items) != 4: + elif len(items) != 5: raise TranspilerError( "Each entry of inst_durations dictionary must be " - "(inst_name, qubits, duration) or " - "(inst_name, qubits, duration, unit)" + "(inst_name, qubits, parameters, duration) or " + "(inst_name, qubits, parameters, duration, unit)" ) - for name, qubits, duration, unit in inst_durations: + for name, qubits, parameters, duration, unit in inst_durations: if isinstance(qubits, int): qubits = [qubits] - if qubits is None: + if qubits is None and parameters is None: self.duration_by_name[name] = duration, unit - else: + elif parameters is None: self.duration_by_name_qubits[(name, tuple(qubits))] = duration, unit + else: + key = (name, tuple(qubits), tuple(parameters)) + self.duration_by_name_qubits_params[key] = duration, int return self @@ -134,13 +139,17 @@ def get( self, inst: Union[str, Instruction], qubits: Union[int, List[int], Qubit, List[Qubit]], + parameters: Optional[List[float]] = None, unit: str = "dt", ) -> float: - """Get the duration of the instruction with the name and the qubits. + """Get the duration of the instruction with the name, qubits, and parameters. + + Some instructions may have a parameter dependent duration. Args: inst: An instruction or its name to be queried. qubits: Qubits or its indices that the instruction acts on. + parameters: The value of the parameters of the desired instruction. unit: The unit of duration to be returned. It must be 's' or 'dt'. Returns: @@ -174,19 +183,31 @@ def get( qubits = [q.index for q in qubits] try: - return self._get(inst_name, qubits, unit) + return self._get(inst_name, qubits, unit, parameters) except TranspilerError as ex: raise TranspilerError( f"Duration of {inst_name} on qubits {qubits} is not found." ) from ex - def _get(self, name: str, qubits: List[int], to_unit: str) -> float: - """Get the duration of the instruction with the name and the qubits.""" + def _get( + self, + name: str, + qubits: List[int], + to_unit: str, + parameters: Optional[Iterable[float]] = None + ) -> float: + """Get the duration of the instruction with the name, qubits, and parameters.""" if name == "barrier": return 0 - key = (name, tuple(qubits)) - if key in self.duration_by_name_qubits: + if parameters is None: + key = (name, tuple(qubits), tuple(parameters)) + else: + key = (name, tuple(qubits)) + + if key in self.duration_by_name_qubits_params: + duration, unit = self.duration_by_name_qubits_params[key] + elif key in self.duration_by_name_qubits: duration, unit = self.duration_by_name_qubits[key] elif name in self.duration_by_name: duration, unit = self.duration_by_name[name] @@ -232,8 +253,8 @@ def units_used(self) -> Set[str]: InstructionDurationsType = Union[ - List[Tuple[str, Optional[Iterable[int]], float, str]], - List[Tuple[str, Optional[Iterable[int]], float]], + List[Tuple[str, Optional[Iterable[int]], Optional[Iterable[float]], float, str]], + List[Tuple[str, Optional[Iterable[int]], Optional[Iterable[float]], float]], InstructionDurations, ] -"""List of tuples representing (instruction name, qubits indices, duration).""" +"""List of tuples representing (instruction name, qubits indices, parameters, duration).""" diff --git a/test/python/circuit/test_scheduled_circuit.py b/test/python/circuit/test_scheduled_circuit.py index 539a5ab9f8fe..c390c91cff2a 100644 --- a/test/python/circuit/test_scheduled_circuit.py +++ b/test/python/circuit/test_scheduled_circuit.py @@ -212,7 +212,11 @@ def test_default_units_for_my_own_duration_users(self): qc, basis_gates=["h", "cx", "delay"], scheduling_method="alap", - instruction_durations=[("h", 0, 200), ("cx", None, 900), ("cx", [0, 1], 800)], + instruction_durations=[ + ("h", 0, None, 200), + ("cx", None, None, 900), + ("cx", [0, 1], None, 800), + ], ) self.assertEqual(scheduled.duration, 1300) @@ -227,7 +231,7 @@ def test_unit_seconds_when_using_backend_durations(self): # update durations durations = InstructionDurations.from_backend(self.backend_with_dt) - durations.update([("cx", [0, 1], 1000 * self.dt, "s")]) + durations.update([("cx", [0, 1], None, 1000 * self.dt, "s")]) scheduled = transpile( qc, backend=self.backend_with_dt, @@ -246,7 +250,7 @@ def test_per_qubit_durations(self): sc = transpile( qc, scheduling_method="alap", - instruction_durations=[("h", None, 200), ("cx", [0, 1], 700)], + instruction_durations=[("h", None, None, 200), ("cx", [0, 1], None, 700)], ) self.assertEqual(sc.qubit_start_time(0), 300) self.assertEqual(sc.qubit_stop_time(0), 1200) @@ -261,7 +265,11 @@ def test_per_qubit_durations(self): sc = transpile( qc, scheduling_method="alap", - instruction_durations=[("h", None, 200), ("cx", [0, 1], 700), ("measure", None, 1000)], + instruction_durations=[ + ("h", None, None, 200), + ("cx", [0, 1], None, 700), + ("measure", None, None, 1000), + ], ) q = sc.qubits self.assertEqual(sc.qubit_start_time(q[0]), 300) diff --git a/test/python/transpiler/test_dynamical_decoupling.py b/test/python/transpiler/test_dynamical_decoupling.py index 2440c013193a..04461d8c1a06 100644 --- a/test/python/transpiler/test_dynamical_decoupling.py +++ b/test/python/transpiler/test_dynamical_decoupling.py @@ -71,16 +71,16 @@ def setUp(self): self.durations = InstructionDurations( [ - ("h", 0, 50), - ("cx", [0, 1], 700), - ("cx", [1, 2], 200), - ("cx", [2, 3], 300), - ("x", None, 50), - ("y", None, 50), - ("u", None, 100), - ("rx", None, 100), - ("measure", None, 1000), - ("reset", None, 1500), + ("h", 0, None, 50), + ("cx", [0, 1], None, 700), + ("cx", [1, 2], None, 200), + ("cx", [2, 3], None, 300), + ("x", None, None, 50), + ("y", None, None, 50), + ("u", None, None, 100), + ("rx", None, None, 100), + ("measure", None, None, 1000), + ("reset", None, None, 1500), ] ) diff --git a/test/python/transpiler/test_instruction_alignments.py b/test/python/transpiler/test_instruction_alignments.py index c9d09ee4af2d..9e9977c55ed7 100644 --- a/test/python/transpiler/test_instruction_alignments.py +++ b/test/python/transpiler/test_instruction_alignments.py @@ -32,15 +32,15 @@ def setUp(self): instruction_durations = InstructionDurations() instruction_durations.update( [ - ("rz", (0,), 0), - ("rz", (1,), 0), - ("x", (0,), 160), - ("x", (1,), 160), - ("sx", (0,), 160), - ("sx", (1,), 160), - ("cx", (0, 1), 800), - ("cx", (1, 0), 800), - ("measure", None, 1600), + ("rz", (0,), None, 0), + ("rz", (1,), None, 0), + ("x", (0,), None, 160), + ("x", (1,), None, 160), + ("sx", (0,), None, 160), + ("sx", (1,), None, 160), + ("cx", (0, 1), None, 800), + ("cx", (1, 0), None, 800), + ("measure", None, None, 1600), ] ) self.time_conversion_pass = TimeUnitConversion(inst_durations=instruction_durations) diff --git a/test/python/transpiler/test_instruction_durations.py b/test/python/transpiler/test_instruction_durations.py index 0c51cf98cec4..069f759d93f4 100644 --- a/test/python/transpiler/test_instruction_durations.py +++ b/test/python/transpiler/test_instruction_durations.py @@ -29,10 +29,10 @@ def test_empty(self): durations = InstructionDurations() self.assertEqual(durations.dt, None) with self.assertRaises(TranspilerError): - durations.get("cx", [0, 1], "dt") + durations.get("cx", [0, 1], None, "dt") def test_fail_if_invalid_dict_is_supplied_when_construction(self): - invalid_dic = [("cx", [0, 1])] # no duration + invalid_dic = [("cx", [0, 1], None)] # no duration with self.assertRaises(TranspilerError): InstructionDurations(invalid_dic) @@ -48,7 +48,7 @@ def test_from_backend_for_backend_without_dt(self): gate = self._find_gate_with_length(backend) durations = InstructionDurations.from_backend(backend) self.assertIsNone(durations.dt) - self.assertGreater(durations.get(gate, 0, "s"), 0) + self.assertGreater(durations.get(gate, 0, unit="s"), 0) with self.assertRaises(TranspilerError): durations.get(gate, 0) diff --git a/test/python/transpiler/test_scheduling_pass.py b/test/python/transpiler/test_scheduling_pass.py index d63dff60d9dc..68f5d62e275a 100644 --- a/test/python/transpiler/test_scheduling_pass.py +++ b/test/python/transpiler/test_scheduling_pass.py @@ -35,7 +35,7 @@ def test_alap_agree_with_reverse_asap_reverse(self): qc.measure_all() durations = InstructionDurations( - [("h", 0, 200), ("cx", [0, 1], 700), ("measure", None, 1000)] + [("h", 0, None, 200), ("cx", [0, 1], None, 700), ("measure", None, None, 1000)] ) pm = PassManager(ALAPSchedule(durations)) @@ -77,7 +77,7 @@ def test_classically_controlled_gate_after_measure(self, schedule_pass): qc.measure(0, 0) qc.x(1).c_if(0, True) - durations = InstructionDurations([("x", None, 200), ("measure", None, 1000)]) + durations = InstructionDurations([("x", None, None, 200), ("measure", None, None, 1000)]) pm = PassManager(schedule_pass(durations)) scheduled = pm.run(qc) @@ -117,7 +117,7 @@ def test_measure_after_measure(self, schedule_pass): qc.measure(0, 0) qc.measure(1, 0) - durations = InstructionDurations([("x", None, 200), ("measure", None, 1000)]) + durations = InstructionDurations([("x", None, None, 200), ("measure", None, None, 1000)]) pm = PassManager(schedule_pass(durations)) scheduled = pm.run(qc) @@ -162,7 +162,7 @@ def test_c_if_on_different_qubits(self, schedule_pass): qc.x(1).c_if(0, True) qc.x(2).c_if(0, True) - durations = InstructionDurations([("x", None, 200), ("measure", None, 1000)]) + durations = InstructionDurations([("x", None, None, 200), ("measure", None, None, 1000)]) pm = PassManager(schedule_pass(durations)) scheduled = pm.run(qc) @@ -202,7 +202,7 @@ def test_shorter_measure_after_measure(self, schedule_pass): qc.measure(0, 0) qc.measure(1, 0) - durations = InstructionDurations([("measure", 0, 1000), ("measure", 1, 700)]) + durations = InstructionDurations([("measure", 0, None, 1000), ("measure", 1, None, 700)]) pm = PassManager(schedule_pass(durations)) scheduled = pm.run(qc) @@ -254,7 +254,7 @@ def test_measure_after_c_if(self): qc.x(1).c_if(0, 1) qc.measure(2, 0) - durations = InstructionDurations([("x", None, 200), ("measure", None, 1000)]) + durations = InstructionDurations([("x", None, None, 200), ("measure", None, None, 1000)]) actual_asap = PassManager(ASAPSchedule(durations)).run(qc) actual_alap = PassManager(ALAPSchedule(durations)).run(qc) From 2f03a02b738a1e44373cae95743107c745f37801 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Mon, 7 Feb 2022 16:36:19 +0100 Subject: [PATCH 02/11] * Adding suggestion by Itoko --- qiskit/transpiler/instruction_durations.py | 14 +++++++++++--- test/python/circuit/test_scheduled_circuit.py | 16 ++++------------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/qiskit/transpiler/instruction_durations.py b/qiskit/transpiler/instruction_durations.py index a37275f61a59..bca90624acd2 100644 --- a/qiskit/transpiler/instruction_durations.py +++ b/qiskit/transpiler/instruction_durations.py @@ -27,7 +27,8 @@ class InstructionDurations: It stores durations (gate lengths) and dt to be used at the scheduling stage of transpiling. It can be constructed from ``backend`` or ``instruction_durations``, - which is an argument of :func:`transpile`. + which is an argument of :func:`transpile`. The duration of an instruction depends on the + instruction (given by name), the qubits, and optionally the parameters of the instruction. """ def __init__( @@ -112,12 +113,19 @@ def update(self, inst_durations: Optional["InstructionDurationsType"], dt: float self.duration_by_name_qubits_params.update(inst_durations.duration_by_name_qubits_params) else: for i, items in enumerate(inst_durations): - if len(items) == 3: + if not isinstance(items[-1], str): inst_durations[i] = (*items, "dt") # set default unit + + elif len(items) == 4: # (inst_name, qubits, duration, unit) + inst_durations[i] = (*items[:2], None, items[3]) + + # assert items = (inst_name, qubits, duration, parameters, unit) elif len(items) != 5: raise TranspilerError( "Each entry of inst_durations dictionary must be " - "(inst_name, qubits, parameters, duration) or " + "(inst_name, qubits, duration) or " + "(inst_name, qubits, duration, unit) or" + "(inst_name, qubits, parameters, duration or" "(inst_name, qubits, parameters, duration, unit)" ) diff --git a/test/python/circuit/test_scheduled_circuit.py b/test/python/circuit/test_scheduled_circuit.py index c390c91cff2a..539a5ab9f8fe 100644 --- a/test/python/circuit/test_scheduled_circuit.py +++ b/test/python/circuit/test_scheduled_circuit.py @@ -212,11 +212,7 @@ def test_default_units_for_my_own_duration_users(self): qc, basis_gates=["h", "cx", "delay"], scheduling_method="alap", - instruction_durations=[ - ("h", 0, None, 200), - ("cx", None, None, 900), - ("cx", [0, 1], None, 800), - ], + instruction_durations=[("h", 0, 200), ("cx", None, 900), ("cx", [0, 1], 800)], ) self.assertEqual(scheduled.duration, 1300) @@ -231,7 +227,7 @@ def test_unit_seconds_when_using_backend_durations(self): # update durations durations = InstructionDurations.from_backend(self.backend_with_dt) - durations.update([("cx", [0, 1], None, 1000 * self.dt, "s")]) + durations.update([("cx", [0, 1], 1000 * self.dt, "s")]) scheduled = transpile( qc, backend=self.backend_with_dt, @@ -250,7 +246,7 @@ def test_per_qubit_durations(self): sc = transpile( qc, scheduling_method="alap", - instruction_durations=[("h", None, None, 200), ("cx", [0, 1], None, 700)], + instruction_durations=[("h", None, 200), ("cx", [0, 1], 700)], ) self.assertEqual(sc.qubit_start_time(0), 300) self.assertEqual(sc.qubit_stop_time(0), 1200) @@ -265,11 +261,7 @@ def test_per_qubit_durations(self): sc = transpile( qc, scheduling_method="alap", - instruction_durations=[ - ("h", None, None, 200), - ("cx", [0, 1], None, 700), - ("measure", None, None, 1000), - ], + instruction_durations=[("h", None, 200), ("cx", [0, 1], 700), ("measure", None, 1000)], ) q = sc.qubits self.assertEqual(sc.qubit_start_time(q[0]), 300) From 064d33889d9b93b411d2038e898122fd32e03eea Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Mon, 7 Feb 2022 17:26:05 +0100 Subject: [PATCH 03/11] * Fix bug where duration and parameters were switched --- qiskit/transpiler/instruction_durations.py | 29 ++++++++++++------- .../python/transpiler/test_scheduling_pass.py | 12 ++++---- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/qiskit/transpiler/instruction_durations.py b/qiskit/transpiler/instruction_durations.py index bca90624acd2..569dacb2d056 100644 --- a/qiskit/transpiler/instruction_durations.py +++ b/qiskit/transpiler/instruction_durations.py @@ -113,27 +113,34 @@ def update(self, inst_durations: Optional["InstructionDurationsType"], dt: float self.duration_by_name_qubits_params.update(inst_durations.duration_by_name_qubits_params) else: for i, items in enumerate(inst_durations): + if not isinstance(items[-1], str): - inst_durations[i] = (*items, "dt") # set default unit + items = (*items, "dt") # set default unit - elif len(items) == 4: # (inst_name, qubits, duration, unit) - inst_durations[i] = (*items[:2], None, items[3]) + if len(items) == 4: # (inst_name, qubits, duration, unit) + inst_durations[i] = (*items[:3], None, items[3]) + else: + inst_durations[i] = items - # assert items = (inst_name, qubits, duration, parameters, unit) - elif len(items) != 5: + # assert (inst_name, qubits, duration, parameters, unit) + if len(inst_durations[i]) != 5: raise TranspilerError( "Each entry of inst_durations dictionary must be " "(inst_name, qubits, duration) or " "(inst_name, qubits, duration, unit) or" - "(inst_name, qubits, parameters, duration or" - "(inst_name, qubits, parameters, duration, unit)" + "(inst_name, qubits, duration, parameters) or" + "(inst_name, qubits, duration, parameters, unit) " + f"received {inst_durations[i]}." ) - for name, qubits, parameters, duration, unit in inst_durations: + for name, qubits, duration, parameters, unit in inst_durations: if isinstance(qubits, int): qubits = [qubits] - if qubits is None and parameters is None: + if isinstance(parameters, (int, float)): + parameters = [parameters] + + if qubits is None: self.duration_by_name[name] = duration, unit elif parameters is None: self.duration_by_name_qubits[(name, tuple(qubits))] = duration, unit @@ -208,7 +215,7 @@ def _get( if name == "barrier": return 0 - if parameters is None: + if parameters is not None: key = (name, tuple(qubits), tuple(parameters)) else: key = (name, tuple(qubits)) @@ -263,6 +270,8 @@ def units_used(self) -> Set[str]: InstructionDurationsType = Union[ List[Tuple[str, Optional[Iterable[int]], Optional[Iterable[float]], float, str]], List[Tuple[str, Optional[Iterable[int]], Optional[Iterable[float]], float]], + List[Tuple[str, Optional[Iterable[int]], float, str]], + List[Tuple[str, Optional[Iterable[int]], float]], InstructionDurations, ] """List of tuples representing (instruction name, qubits indices, parameters, duration).""" diff --git a/test/python/transpiler/test_scheduling_pass.py b/test/python/transpiler/test_scheduling_pass.py index 68f5d62e275a..d63dff60d9dc 100644 --- a/test/python/transpiler/test_scheduling_pass.py +++ b/test/python/transpiler/test_scheduling_pass.py @@ -35,7 +35,7 @@ def test_alap_agree_with_reverse_asap_reverse(self): qc.measure_all() durations = InstructionDurations( - [("h", 0, None, 200), ("cx", [0, 1], None, 700), ("measure", None, None, 1000)] + [("h", 0, 200), ("cx", [0, 1], 700), ("measure", None, 1000)] ) pm = PassManager(ALAPSchedule(durations)) @@ -77,7 +77,7 @@ def test_classically_controlled_gate_after_measure(self, schedule_pass): qc.measure(0, 0) qc.x(1).c_if(0, True) - durations = InstructionDurations([("x", None, None, 200), ("measure", None, None, 1000)]) + durations = InstructionDurations([("x", None, 200), ("measure", None, 1000)]) pm = PassManager(schedule_pass(durations)) scheduled = pm.run(qc) @@ -117,7 +117,7 @@ def test_measure_after_measure(self, schedule_pass): qc.measure(0, 0) qc.measure(1, 0) - durations = InstructionDurations([("x", None, None, 200), ("measure", None, None, 1000)]) + durations = InstructionDurations([("x", None, 200), ("measure", None, 1000)]) pm = PassManager(schedule_pass(durations)) scheduled = pm.run(qc) @@ -162,7 +162,7 @@ def test_c_if_on_different_qubits(self, schedule_pass): qc.x(1).c_if(0, True) qc.x(2).c_if(0, True) - durations = InstructionDurations([("x", None, None, 200), ("measure", None, None, 1000)]) + durations = InstructionDurations([("x", None, 200), ("measure", None, 1000)]) pm = PassManager(schedule_pass(durations)) scheduled = pm.run(qc) @@ -202,7 +202,7 @@ def test_shorter_measure_after_measure(self, schedule_pass): qc.measure(0, 0) qc.measure(1, 0) - durations = InstructionDurations([("measure", 0, None, 1000), ("measure", 1, None, 700)]) + durations = InstructionDurations([("measure", 0, 1000), ("measure", 1, 700)]) pm = PassManager(schedule_pass(durations)) scheduled = pm.run(qc) @@ -254,7 +254,7 @@ def test_measure_after_c_if(self): qc.x(1).c_if(0, 1) qc.measure(2, 0) - durations = InstructionDurations([("x", None, None, 200), ("measure", None, None, 1000)]) + durations = InstructionDurations([("x", None, 200), ("measure", None, 1000)]) actual_asap = PassManager(ASAPSchedule(durations)).run(qc) actual_alap = PassManager(ALAPSchedule(durations)).run(qc) From 9fcd3c131daad458d966565068228cff216de157 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Mon, 7 Feb 2022 17:30:22 +0100 Subject: [PATCH 04/11] * Remove None from tests. --- .../transpiler/test_dynamical_decoupling.py | 20 +++++++++---------- .../transpiler/test_instruction_alignments.py | 18 ++++++++--------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/test/python/transpiler/test_dynamical_decoupling.py b/test/python/transpiler/test_dynamical_decoupling.py index 04461d8c1a06..2440c013193a 100644 --- a/test/python/transpiler/test_dynamical_decoupling.py +++ b/test/python/transpiler/test_dynamical_decoupling.py @@ -71,16 +71,16 @@ def setUp(self): self.durations = InstructionDurations( [ - ("h", 0, None, 50), - ("cx", [0, 1], None, 700), - ("cx", [1, 2], None, 200), - ("cx", [2, 3], None, 300), - ("x", None, None, 50), - ("y", None, None, 50), - ("u", None, None, 100), - ("rx", None, None, 100), - ("measure", None, None, 1000), - ("reset", None, None, 1500), + ("h", 0, 50), + ("cx", [0, 1], 700), + ("cx", [1, 2], 200), + ("cx", [2, 3], 300), + ("x", None, 50), + ("y", None, 50), + ("u", None, 100), + ("rx", None, 100), + ("measure", None, 1000), + ("reset", None, 1500), ] ) diff --git a/test/python/transpiler/test_instruction_alignments.py b/test/python/transpiler/test_instruction_alignments.py index 9e9977c55ed7..c9d09ee4af2d 100644 --- a/test/python/transpiler/test_instruction_alignments.py +++ b/test/python/transpiler/test_instruction_alignments.py @@ -32,15 +32,15 @@ def setUp(self): instruction_durations = InstructionDurations() instruction_durations.update( [ - ("rz", (0,), None, 0), - ("rz", (1,), None, 0), - ("x", (0,), None, 160), - ("x", (1,), None, 160), - ("sx", (0,), None, 160), - ("sx", (1,), None, 160), - ("cx", (0, 1), None, 800), - ("cx", (1, 0), None, 800), - ("measure", None, None, 1600), + ("rz", (0,), 0), + ("rz", (1,), 0), + ("x", (0,), 160), + ("x", (1,), 160), + ("sx", (0,), 160), + ("sx", (1,), 160), + ("cx", (0, 1), 800), + ("cx", (1, 0), 800), + ("measure", None, 1600), ] ) self.time_conversion_pass = TimeUnitConversion(inst_durations=instruction_durations) From c6791ec31f1cc1b147a7b5f8534053a3338c0471 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Tue, 8 Feb 2022 11:19:16 +0100 Subject: [PATCH 05/11] * black. --- qiskit/transpiler/instruction_durations.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/qiskit/transpiler/instruction_durations.py b/qiskit/transpiler/instruction_durations.py index 569dacb2d056..72cdb0cb6bf8 100644 --- a/qiskit/transpiler/instruction_durations.py +++ b/qiskit/transpiler/instruction_durations.py @@ -110,7 +110,9 @@ def update(self, inst_durations: Optional["InstructionDurationsType"], dt: float if isinstance(inst_durations, InstructionDurations): self.duration_by_name.update(inst_durations.duration_by_name) self.duration_by_name_qubits.update(inst_durations.duration_by_name_qubits) - self.duration_by_name_qubits_params.update(inst_durations.duration_by_name_qubits_params) + self.duration_by_name_qubits_params.update( + inst_durations.duration_by_name_qubits_params + ) else: for i, items in enumerate(inst_durations): @@ -209,7 +211,7 @@ def _get( name: str, qubits: List[int], to_unit: str, - parameters: Optional[Iterable[float]] = None + parameters: Optional[Iterable[float]] = None, ) -> float: """Get the duration of the instruction with the name, qubits, and parameters.""" if name == "barrier": From 9f090da8feff59e4b1a8ef9dfe39e2e7e442610a Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Tue, 8 Feb 2022 12:50:01 +0100 Subject: [PATCH 06/11] * Added check on None duration. --- qiskit/transpiler/instruction_durations.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qiskit/transpiler/instruction_durations.py b/qiskit/transpiler/instruction_durations.py index 72cdb0cb6bf8..b3269b2bf0cb 100644 --- a/qiskit/transpiler/instruction_durations.py +++ b/qiskit/transpiler/instruction_durations.py @@ -135,6 +135,9 @@ def update(self, inst_durations: Optional["InstructionDurationsType"], dt: float f"received {inst_durations[i]}." ) + if inst_durations[i][2] is None: + raise TranspilerError(f"None duration for {inst_durations[i]}.") + for name, qubits, duration, parameters, unit in inst_durations: if isinstance(qubits, int): qubits = [qubits] From f335fe07ea78a3b400dfd2ed35bf356f0d250ce3 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Tue, 8 Feb 2022 13:41:52 +0100 Subject: [PATCH 07/11] * Added test. --- qiskit/transpiler/instruction_durations.py | 10 ++-- .../transpiler/test_instruction_durations.py | 51 +++++++++++++++++-- 2 files changed, 52 insertions(+), 9 deletions(-) diff --git a/qiskit/transpiler/instruction_durations.py b/qiskit/transpiler/instruction_durations.py index b3269b2bf0cb..33cd092133c0 100644 --- a/qiskit/transpiler/instruction_durations.py +++ b/qiskit/transpiler/instruction_durations.py @@ -151,7 +151,7 @@ def update(self, inst_durations: Optional["InstructionDurationsType"], dt: float self.duration_by_name_qubits[(name, tuple(qubits))] = duration, unit else: key = (name, tuple(qubits), tuple(parameters)) - self.duration_by_name_qubits_params[key] = duration, int + self.duration_by_name_qubits_params[key] = duration, unit return self @@ -159,8 +159,8 @@ def get( self, inst: Union[str, Instruction], qubits: Union[int, List[int], Qubit, List[Qubit]], - parameters: Optional[List[float]] = None, unit: str = "dt", + parameters: Optional[List[float]] = None, ) -> float: """Get the duration of the instruction with the name, qubits, and parameters. @@ -169,8 +169,8 @@ def get( Args: inst: An instruction or its name to be queried. qubits: Qubits or its indices that the instruction acts on. - parameters: The value of the parameters of the desired instruction. unit: The unit of duration to be returned. It must be 's' or 'dt'. + parameters: The value of the parameters of the desired instruction. Returns: float|int: The duration of the instruction on the qubits. @@ -273,8 +273,8 @@ def units_used(self) -> Set[str]: InstructionDurationsType = Union[ - List[Tuple[str, Optional[Iterable[int]], Optional[Iterable[float]], float, str]], - List[Tuple[str, Optional[Iterable[int]], Optional[Iterable[float]], float]], + List[Tuple[str, Optional[Iterable[int]], float, Optional[Iterable[float]], str]], + List[Tuple[str, Optional[Iterable[int]], float, Optional[Iterable[float]]]], List[Tuple[str, Optional[Iterable[int]], float, str]], List[Tuple[str, Optional[Iterable[int]], float]], InstructionDurations, diff --git a/test/python/transpiler/test_instruction_durations.py b/test/python/transpiler/test_instruction_durations.py index 069f759d93f4..d0e8b365b2e3 100644 --- a/test/python/transpiler/test_instruction_durations.py +++ b/test/python/transpiler/test_instruction_durations.py @@ -14,10 +14,17 @@ """Test InstructionDurations class.""" -from qiskit.circuit import Delay, Parameter +from ddt import ddt, data + +from qiskit.circuit import Delay, Parameter, QuantumCircuit +from qiskit.circuit.library import XGate from qiskit.test.mock.backends import FakeParis, FakeTokyo from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.instruction_durations import InstructionDurations +from qiskit.transpiler.passes import ALAPSchedule, DynamicalDecoupling +from qiskit.transpiler import PassManager + +import qiskit.pulse as pulse from qiskit.test.base import QiskitTestCase @@ -29,10 +36,10 @@ def test_empty(self): durations = InstructionDurations() self.assertEqual(durations.dt, None) with self.assertRaises(TranspilerError): - durations.get("cx", [0, 1], None, "dt") + durations.get("cx", [0, 1], "dt") def test_fail_if_invalid_dict_is_supplied_when_construction(self): - invalid_dic = [("cx", [0, 1], None)] # no duration + invalid_dic = [("cx", [0, 1])] # no duration with self.assertRaises(TranspilerError): InstructionDurations(invalid_dic) @@ -48,10 +55,18 @@ def test_from_backend_for_backend_without_dt(self): gate = self._find_gate_with_length(backend) durations = InstructionDurations.from_backend(backend) self.assertIsNone(durations.dt) - self.assertGreater(durations.get(gate, 0, unit="s"), 0) + self.assertGreater(durations.get(gate, 0, "s"), 0) with self.assertRaises(TranspilerError): durations.get(gate, 0) + def test_update_with_parameters(self): + durations = InstructionDurations( + [("rzx", (0, 1), 150, (0.5,)), ("rzx", (0, 1), 300, (1.0,))] + ) + + self.assertEqual(durations.get("rzx", [0, 1], parameters=[0.5]), 150) + self.assertEqual(durations.get("rzx", [0, 1], parameters=[1.0]), 300) + def _find_gate_with_length(self, backend): """Find a gate that has gate length.""" props = backend.properties() @@ -80,3 +95,31 @@ def test_fail_if_get_unbounded_duration_with_unit_conversion_when_dt_is_not_prov parameterized_delay = Delay(param, "s") with self.assertRaises(TranspilerError): InstructionDurations().get(parameterized_delay, 0) + + +@ddt +class TestTranspile(QiskitTestCase): + """Test InstructionDurations with calibrations and DD.""" + + @data(0.5, 1.5) + def test_dd_with_calibrations_with_parameters(self, param_value): + """Check that calibrations in a circuit with parameter work fine.""" + + circ = QuantumCircuit(2) + circ.x(0) + circ.cx(0, 1) + circ.rx(param_value, 1) + + rx_duration = int(param_value * 1000) + + with pulse.build() as rx: + pulse.play(pulse.Gaussian(rx_duration, 0.1, rx_duration // 4), pulse.DriveChannel(1)) + + circ.add_calibration("rx", (1,), rx, params=[param_value]) + + durations = InstructionDurations([("x", None, 100), ("cx", None, 300)]) + + dd_sequence = [XGate(), XGate()] + pm = PassManager([ALAPSchedule(durations), DynamicalDecoupling(durations, dd_sequence)]) + + self.assertEqual(pm.run([circ])[0].duration, rx_duration + 100 + 300) From 7445a27704b0bcc51150ec472c0e0ee3de36418b Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Tue, 8 Feb 2022 13:45:51 +0100 Subject: [PATCH 08/11] * Reno --- ...nstruction-durations-8d98369f89b48279.yaml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 releasenotes/notes/instruction-durations-8d98369f89b48279.yaml diff --git a/releasenotes/notes/instruction-durations-8d98369f89b48279.yaml b/releasenotes/notes/instruction-durations-8d98369f89b48279.yaml new file mode 100644 index 000000000000..9450f35fe6fd --- /dev/null +++ b/releasenotes/notes/instruction-durations-8d98369f89b48279.yaml @@ -0,0 +1,19 @@ +--- +upgrade: + - | + The `InstructionDurations` class is upgraded to accept gate parameters. Now, + an instruction duration is a tuple of `(inst_name, qubits, duration, parameters, unit)`. + This makes it possible to run transpiler passes such as Dynamical Decoupling + on circuits that have pulse gates with parameters such as + + .. parsed-literal:: + + circ = QuantumCircuit(2) + circ.x(0) + circ.cx(0, 1) + circ.rx(1.5, 1) + + with pulse.build() as rx: + pulse.play(pulse.Gaussian(2000, 0.1, 500), pulse.DriveChannel(1)) + + circ.add_calibration("rx", (1, ), rx, params = [1.5]) From a05a523564cef70436f9a4143040d45526fe8dd8 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Tue, 8 Feb 2022 15:19:02 +0100 Subject: [PATCH 09/11] * Test fix. --- test/python/transpiler/test_instruction_durations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/transpiler/test_instruction_durations.py b/test/python/transpiler/test_instruction_durations.py index d0e8b365b2e3..ceb4688e207e 100644 --- a/test/python/transpiler/test_instruction_durations.py +++ b/test/python/transpiler/test_instruction_durations.py @@ -122,4 +122,4 @@ def test_dd_with_calibrations_with_parameters(self, param_value): dd_sequence = [XGate(), XGate()] pm = PassManager([ALAPSchedule(durations), DynamicalDecoupling(durations, dd_sequence)]) - self.assertEqual(pm.run([circ])[0].duration, rx_duration + 100 + 300) + self.assertEqual(pm.run(circ).duration, rx_duration + 100 + 300) From e616d77bb3af9917737671240e5a0fa196888763 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 11 Feb 2022 14:46:24 +0100 Subject: [PATCH 10/11] * Moved test and updated reno. --- ...nstruction-durations-8d98369f89b48279.yaml | 14 ------- .../transpiler/test_dynamical_decoupling.py | 27 ++++++++++++++ .../transpiler/test_instruction_durations.py | 37 +------------------ 3 files changed, 28 insertions(+), 50 deletions(-) diff --git a/releasenotes/notes/instruction-durations-8d98369f89b48279.yaml b/releasenotes/notes/instruction-durations-8d98369f89b48279.yaml index 9450f35fe6fd..c671ae706bfe 100644 --- a/releasenotes/notes/instruction-durations-8d98369f89b48279.yaml +++ b/releasenotes/notes/instruction-durations-8d98369f89b48279.yaml @@ -3,17 +3,3 @@ upgrade: - | The `InstructionDurations` class is upgraded to accept gate parameters. Now, an instruction duration is a tuple of `(inst_name, qubits, duration, parameters, unit)`. - This makes it possible to run transpiler passes such as Dynamical Decoupling - on circuits that have pulse gates with parameters such as - - .. parsed-literal:: - - circ = QuantumCircuit(2) - circ.x(0) - circ.cx(0, 1) - circ.rx(1.5, 1) - - with pulse.build() as rx: - pulse.play(pulse.Gaussian(2000, 0.1, 500), pulse.DriveChannel(1)) - - circ.add_calibration("rx", (1, ), rx, params = [1.5]) diff --git a/test/python/transpiler/test_dynamical_decoupling.py b/test/python/transpiler/test_dynamical_decoupling.py index 2440c013193a..16722a656f0a 100644 --- a/test/python/transpiler/test_dynamical_decoupling.py +++ b/test/python/transpiler/test_dynamical_decoupling.py @@ -15,6 +15,7 @@ import unittest import numpy as np from numpy import pi +from ddt import ddt, data from qiskit.circuit import QuantumCircuit, Delay from qiskit.circuit.library import XGate, YGate, RXGate, UGate @@ -24,9 +25,12 @@ from qiskit.transpiler.passmanager import PassManager from qiskit.transpiler.exceptions import TranspilerError +import qiskit.pulse as pulse + from qiskit.test import QiskitTestCase +@ddt class TestDynamicalDecoupling(QiskitTestCase): """Tests DynamicalDecoupling pass.""" @@ -595,6 +599,29 @@ def test_insert_dd_bad_sequence(self): with self.assertRaises(TranspilerError): pm.run(self.ghz4) + @data(0.5, 1.5) + def test_dd_with_calibrations_with_parameters(self, param_value): + """Check that calibrations in a circuit with parameters work fine.""" + + circ = QuantumCircuit(2) + circ.x(0) + circ.cx(0, 1) + circ.rx(param_value, 1) + + rx_duration = int(param_value * 1000) + + with pulse.build() as rx: + pulse.play(pulse.Gaussian(rx_duration, 0.1, rx_duration // 4), pulse.DriveChannel(1)) + + circ.add_calibration("rx", (1,), rx, params=[param_value]) + + durations = InstructionDurations([("x", None, 100), ("cx", None, 300)]) + + dd_sequence = [XGate(), XGate()] + pm = PassManager([ALAPSchedule(durations), DynamicalDecoupling(durations, dd_sequence)]) + + self.assertEqual(pm.run(circ).duration, rx_duration + 100 + 300) + if __name__ == "__main__": unittest.main() diff --git a/test/python/transpiler/test_instruction_durations.py b/test/python/transpiler/test_instruction_durations.py index ceb4688e207e..e7c296ae89dc 100644 --- a/test/python/transpiler/test_instruction_durations.py +++ b/test/python/transpiler/test_instruction_durations.py @@ -14,17 +14,10 @@ """Test InstructionDurations class.""" -from ddt import ddt, data - -from qiskit.circuit import Delay, Parameter, QuantumCircuit -from qiskit.circuit.library import XGate +from qiskit.circuit import Delay, Parameter from qiskit.test.mock.backends import FakeParis, FakeTokyo from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.instruction_durations import InstructionDurations -from qiskit.transpiler.passes import ALAPSchedule, DynamicalDecoupling -from qiskit.transpiler import PassManager - -import qiskit.pulse as pulse from qiskit.test.base import QiskitTestCase @@ -95,31 +88,3 @@ def test_fail_if_get_unbounded_duration_with_unit_conversion_when_dt_is_not_prov parameterized_delay = Delay(param, "s") with self.assertRaises(TranspilerError): InstructionDurations().get(parameterized_delay, 0) - - -@ddt -class TestTranspile(QiskitTestCase): - """Test InstructionDurations with calibrations and DD.""" - - @data(0.5, 1.5) - def test_dd_with_calibrations_with_parameters(self, param_value): - """Check that calibrations in a circuit with parameter work fine.""" - - circ = QuantumCircuit(2) - circ.x(0) - circ.cx(0, 1) - circ.rx(param_value, 1) - - rx_duration = int(param_value * 1000) - - with pulse.build() as rx: - pulse.play(pulse.Gaussian(rx_duration, 0.1, rx_duration // 4), pulse.DriveChannel(1)) - - circ.add_calibration("rx", (1,), rx, params=[param_value]) - - durations = InstructionDurations([("x", None, 100), ("cx", None, 300)]) - - dd_sequence = [XGate(), XGate()] - pm = PassManager([ALAPSchedule(durations), DynamicalDecoupling(durations, dd_sequence)]) - - self.assertEqual(pm.run(circ).duration, rx_duration + 100 + 300) From df388c76321d6c8c356e2d1daf3b102a9faf212e Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Tue, 22 Feb 2022 13:56:59 +0100 Subject: [PATCH 11/11] * Docstring. --- qiskit/transpiler/instruction_durations.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qiskit/transpiler/instruction_durations.py b/qiskit/transpiler/instruction_durations.py index 33cd092133c0..bc43c78b68d9 100644 --- a/qiskit/transpiler/instruction_durations.py +++ b/qiskit/transpiler/instruction_durations.py @@ -29,6 +29,9 @@ class InstructionDurations: It can be constructed from ``backend`` or ``instruction_durations``, which is an argument of :func:`transpile`. The duration of an instruction depends on the instruction (given by name), the qubits, and optionally the parameters of the instruction. + Note that these fields are used as keys in dictionaries that are used to retrieve the + instruction durations. Therefore, users must use the exact same parameter value to retrieve + an instruction duration as the value with which it was added. """ def __init__(