Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleanup timeline drawer with schedule analysis pass. #7935

Merged
merged 11 commits into from
Jun 16, 2022
18 changes: 18 additions & 0 deletions qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ def __init__(
# Data contains a list of instructions and their contexts,
# in the order they were applied.
self._data = []
self._op_start_times = []
nkanazawa1989 marked this conversation as resolved.
Show resolved Hide resolved

# A stack to hold the instruction sets that are being built up during for-, if- and
# while-block construction. These are stored as a stripped down sequence of instructions,
Expand Down Expand Up @@ -299,6 +300,23 @@ def data(self) -> QuantumCircuitData:
"""
return QuantumCircuitData(self)

@property
def op_start_times(self) -> List[int]:
"""Return a list of operation start times.

This attribute is enabled once one of scheduling analysis passes
runs on the quantum circuit.

Returns:
List of integers representing instruction start times.
The index corresponds to the index of instruction in :attr:`QuantumCircuit.data`.
"""
if not self._op_start_times:
nkanazawa1989 marked this conversation as resolved.
Show resolved Hide resolved
raise AttributeError(
"This circuit is not scheduled. Run one of scheduling passes."
nkanazawa1989 marked this conversation as resolved.
Show resolved Hide resolved
)
return self._op_start_times

@data.setter
def data(
self, data_input: List[Tuple[Instruction, List[QubitSpecifier], List[ClbitSpecifier]]]
Expand Down
9 changes: 9 additions & 0 deletions qiskit/transpiler/basepasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,15 @@ def __call__(self, circuit, property_set=None):
result_circuit._clbit_write_latency = self.property_set["clbit_write_latency"]
if self.property_set["conditional_latency"] is not None:
result_circuit._conditional_latency = self.property_set["conditional_latency"]
if self.property_set["node_start_time"]:
# This is dictionary keyed on the DAGOpNode, which is invalidated once
# dag is converted into circuit. So this schedule information is
# also converted into list with the same ordering with circuit.data.
topological_start_times = []
start_times = self.property_set["node_start_time"]
for dag_node in result.topological_op_nodes():
topological_start_times.append(start_times[dag_node])
result_circuit._op_start_times = self.property_set["node_start_time"]

return result_circuit

Expand Down
10 changes: 10 additions & 0 deletions qiskit/transpiler/runningpassmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,16 @@ def run(self, circuit, output_name=None, callback=None):
circuit._clbit_write_latency = self.property_set["clbit_write_latency"]
circuit._conditional_latency = self.property_set["conditional_latency"]

if self.property_set["node_start_time"]:
# This is dictionary keyed on the DAGOpNode, which is invalidated once
# dag is converted into circuit. So this schedule information is
# also converted into list with the same ordering with circuit.data.
topological_start_times = []
start_times = self.property_set["node_start_time"]
for dag_node in dag.topological_op_nodes():
topological_start_times.append(start_times[dag_node])
circuit._op_start_times = topological_start_times

return circuit

def _do_pass(self, pass_, dag, options):
Expand Down
72 changes: 39 additions & 33 deletions qiskit/visualization/timeline/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,14 @@

from copy import deepcopy
from functools import partial
from itertools import chain
from typing import Tuple, Iterator, Dict
from enum import Enum

import numpy as np

from qiskit import circuit
from qiskit.visualization.exceptions import VisualizationError
from qiskit.visualization.timeline import drawings, events, types
from qiskit.visualization.timeline import drawings, types
from qiskit.visualization.timeline.stylesheet import QiskitTimelineStyle


Expand Down Expand Up @@ -143,43 +142,50 @@ def load_program(self, program: circuit.QuantumCircuit):
Args:
program: Scheduled circuit object to draw.
"""
self.bits = program.qubits + program.clbits
stop_time = 0
not_gate_like = (circuit.Barrier,)

for t0, (inst, qargs, cargs) in zip(program.op_start_times, program.data):
bits = qargs + cargs
for bit_pos, bit in enumerate(qargs + cargs):
if not isinstance(inst, not_gate_like):
# Generate draw object for gates
gate_source = types.ScheduledGate(
t0=t0,
operand=inst,
duration=inst.duration,
bits=bits,
bit_position=bit_pos,
)
for gen in self.generator["gates"]:
obj_generator = partial(gen, formatter=self.formatter)
for datum in obj_generator(gate_source):
self.add_data(datum)
if len(bits) > 1 and bit_pos == 0:
# Generate draw object for gate-gate link
line_pos = t0 + 0.5 * inst.duration
link_source = types.GateLink(t0=line_pos, opname=inst.name, bits=bits)
for gen in self.generator["gate_links"]:
obj_generator = partial(gen, formatter=self.formatter)
for datum in obj_generator(link_source):
self.add_data(datum)
if isinstance(inst, circuit.Barrier):
# Generate draw object for barrier
barrier_source = types.Barrier(t0=t0, bits=bits, bit_position=bit_pos)
for gen in self.generator["barriers"]:
obj_generator = partial(gen, formatter=self.formatter)
for datum in obj_generator(barrier_source):
self.add_data(datum)

self.bits = program.qubits + program.clbits
for bit in self.bits:
bit_events = events.BitEvents.load_program(scheduled_circuit=program, bit=bit)

# create objects associated with gates
for gen in self.generator["gates"]:
obj_generator = partial(gen, formatter=self.formatter)
draw_targets = [obj_generator(gate) for gate in bit_events.get_gates()]
for data in list(chain.from_iterable(draw_targets)):
self.add_data(data)

# create objects associated with gate links
for gen in self.generator["gate_links"]:
obj_generator = partial(gen, formatter=self.formatter)
draw_targets = [obj_generator(link) for link in bit_events.get_gate_links()]
for data in list(chain.from_iterable(draw_targets)):
self.add_data(data)

# create objects associated with barrier
for gen in self.generator["barriers"]:
obj_generator = partial(gen, formatter=self.formatter)
draw_targets = [obj_generator(barrier) for barrier in bit_events.get_barriers()]
for data in list(chain.from_iterable(draw_targets)):
self.add_data(data)

# create objects associated with bit
for gen in self.generator["bits"]:
# Generate draw objects for bit
obj_generator = partial(gen, formatter=self.formatter)
for data in obj_generator(bit):
self.add_data(data)

stop_time = max(stop_time, bit_events.stop_time)
for datum in obj_generator(bit):
self.add_data(datum)

# update time range
t_end = max(stop_time, self.formatter["margin.minimum_duration"])
t_end = max(program.duration, self.formatter["margin.minimum_duration"])
self.set_time_range(t_start=0, t_end=t_end)

def set_time_range(self, t_start: int, t_end: int):
Expand Down
121 changes: 0 additions & 121 deletions qiskit/visualization/timeline/events.py

This file was deleted.

27 changes: 27 additions & 0 deletions releasenotes/notes/cleanup-timeline-drawer-a6287bdab4459e6e.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
features:
- |
New attribute :attr:`op_start_times` has been added to :class:`~QuantumCircuit`.
This information is populated when one of scheduling analysis passes is run on the circuit.
It can be used to obtain circuit instruction with instruction time, for example:

.. code-block:: python

from qiskit import QuantumCircuit, transpile
from qiskit.test.mock import FakeMontreal

backend = FakeMontreal()

qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)

qct = transpile(
qc, backend, initial_layout=[0, 1], coupling_map=[[0, 1]], scheduling_method="alap"
)
scheduled_insts = list(zip(qct.op_start_times, qct.data))

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you want to add a deprecation note about visualizing a timeline for an unscheduled/untranspiled circuit?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Matthew I've forgotten to write. Added in 8891220

fixes:
- |
Time misalignment bug of drawing classical register with :func:`~timeline_drawer`
has been fixed. Now classical register slots are drawn at correct position.
Loading