Skip to content

Commit

Permalink
Merge branch 'main' into sabre-no-barrier
Browse files Browse the repository at this point in the history
  • Loading branch information
mtreinish authored Nov 22, 2023
2 parents 89da56e + 8a58b3a commit 34e4a20
Show file tree
Hide file tree
Showing 17 changed files with 370 additions and 128 deletions.
2 changes: 2 additions & 0 deletions qiskit/transpiler/passes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@
GatesInBasis
ConvertConditionsToIfOps
UnrollForLoops
FilterOpNodes
"""

# layout selection (placement)
Expand Down Expand Up @@ -292,3 +293,4 @@
from .utils import GatesInBasis
from .utils import ConvertConditionsToIfOps
from .utils import UnrollForLoops
from .utils import FilterOpNodes
1 change: 1 addition & 0 deletions qiskit/transpiler/passes/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from .convert_conditions_to_if_ops import ConvertConditionsToIfOps
from .unroll_forloops import UnrollForLoops
from .minimum_point import MinimumPoint
from .filter_op_nodes import FilterOpNodes

# Utility functions
from . import control_flow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ class BarrierBeforeFinalMeasurements(TransformationPass):
other measurements or barriers.)
"""

def __init__(self, label=None):
super().__init__()
self.label = label

def run(self, dag):
"""Run the BarrierBeforeFinalMeasurements pass on `dag`."""
# Collect DAG nodes which are followed only by barriers or other measures.
Expand Down Expand Up @@ -64,7 +68,7 @@ def run(self, dag):
final_qubits = dag.qubits

barrier_layer.apply_operation_back(
Barrier(len(final_qubits)), final_qubits, (), check=False
Barrier(len(final_qubits), label=self.label), final_qubits, (), check=False
)

# Preserve order of final ops collected earlier from the original DAG.
Expand All @@ -83,6 +87,9 @@ def run(self, dag):

dag.compose(barrier_layer)

# Merge the new barrier into any other barriers
adjacent_pass = MergeAdjacentBarriers()
return adjacent_pass.run(dag)
if self.label is None:
# Merge the new barrier into any other barriers
adjacent_pass = MergeAdjacentBarriers()
return adjacent_pass.run(dag)
else:
return dag
65 changes: 65 additions & 0 deletions qiskit/transpiler/passes/utils/filter_op_nodes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2023.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Filter ops from a circuit"""

from typing import Callable

from qiskit.dagcircuit import DAGCircuit, DAGOpNode
from qiskit.transpiler.basepasses import TransformationPass
from qiskit.transpiler.passes.utils import control_flow


class FilterOpNodes(TransformationPass):
"""Remove all operations that match a filter function
This transformation pass is used to remove any operations that matches a
the provided filter function.
Args:
predicate: A given callable that will be passed the :class:`.DAGOpNode`
for each node in the :class:`.DAGCircuit`. If the callable returns
``True`` the :class:`.DAGOpNode` is retained in the circuit and if it
returns ``False`` it is removed from the circuit.
Example:
Filter out operations that are labelled ``"foo"``
.. plot::
:include-source:
from qiskit import QuantumCircuit
from qiskit.transpiler.passes import FilterOpNodes
circuit = QuantumCircuit(1)
circuit.x(0, label='foo')
circuit.barrier()
circuit.h(0)
circuit = FilterOpNodes(
lambda node: getattr(node.op, "label") != "foo"
)(circuit)
circuit.draw('mpl')
"""

def __init__(self, predicate: Callable[[DAGOpNode], bool]):
super().__init__()
self.predicate = predicate

@control_flow.trivial_recurse
def run(self, dag: DAGCircuit) -> DAGCircuit:
"""Run the RemoveBarriers pass on `dag`."""
for node in dag.op_nodes():
if not self.predicate(node):
dag.remove_op_node(node)
return dag
32 changes: 28 additions & 4 deletions qiskit/transpiler/preset_passmanagers/builtin_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -684,7 +684,13 @@ def _swap_mapped(property_set):
and pass_manager_config.routing_method != "sabre",
)
layout.append(
[BarrierBeforeFinalMeasurements(), choose_layout_2], condition=_vf2_match_not_found
[
BarrierBeforeFinalMeasurements(
"qiskit.transpiler.internal.routing.protection.barrier"
),
choose_layout_2,
],
condition=_vf2_match_not_found,
)
elif optimization_level == 2:
choose_layout_0 = VF2Layout(
Expand All @@ -706,7 +712,13 @@ def _swap_mapped(property_set):
and pass_manager_config.routing_method != "sabre",
)
layout.append(
[BarrierBeforeFinalMeasurements(), choose_layout_1], condition=_vf2_match_not_found
[
BarrierBeforeFinalMeasurements(
"qiskit.transpiler.internal.routing.protection.barrier"
),
choose_layout_1,
],
condition=_vf2_match_not_found,
)
elif optimization_level == 3:
choose_layout_0 = VF2Layout(
Expand All @@ -728,7 +740,13 @@ def _swap_mapped(property_set):
and pass_manager_config.routing_method != "sabre",
)
layout.append(
[BarrierBeforeFinalMeasurements(), choose_layout_1], condition=_vf2_match_not_found
[
BarrierBeforeFinalMeasurements(
"qiskit.transpiler.internal.routing.protection.barrier"
),
choose_layout_1,
],
condition=_vf2_match_not_found,
)
else:
raise TranspilerError(f"Invalid optimization level: {optimization_level}")
Expand Down Expand Up @@ -882,7 +900,13 @@ def _swap_mapped(property_set):
else:
raise TranspilerError(f"Invalid optimization level: {optimization_level}")
layout.append(
[BarrierBeforeFinalMeasurements(), layout_pass], condition=_choose_layout_condition
[
BarrierBeforeFinalMeasurements(
"qiskit.transpiler.internal.routing.protection.barrier"
),
layout_pass,
],
condition=_choose_layout_condition,
)
embed = common.generate_embed_passmanager(coupling_map)
layout.append(
Expand Down
19 changes: 18 additions & 1 deletion qiskit/transpiler/preset_passmanagers/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
from qiskit.transpiler.passes import EnlargeWithAncilla
from qiskit.transpiler.passes import ApplyLayout
from qiskit.transpiler.passes import RemoveResetInZeroState
from qiskit.transpiler.passes import FilterOpNodes
from qiskit.transpiler.passes import ValidatePulseGates
from qiskit.transpiler.passes import PadDelay
from qiskit.transpiler.passes import InstructionDurationCheck
Expand Down Expand Up @@ -328,7 +329,15 @@ def _swap_condition(property_set):
return not property_set["routing_not_needed"]

if use_barrier_before_measurement:
routing.append([BarrierBeforeFinalMeasurements(), routing_pass], condition=_swap_condition)
routing.append(
[
BarrierBeforeFinalMeasurements(
label="qiskit.transpiler.internal.routing.protection.barrier"
),
routing_pass,
],
condition=_swap_condition,
)
else:
routing.append([routing_pass], condition=_swap_condition)

Expand All @@ -348,6 +357,14 @@ def _swap_condition(property_set):
)
routing.append(ApplyLayout(), condition=_apply_post_layout_condition)

def filter_fn(node):
return (
getattr(node.op, "label", None)
!= "qiskit.transpiler.internal.routing.protection.barrier"
)

routing.append([FilterOpNodes(filter_fn)])

return routing


Expand Down
49 changes: 2 additions & 47 deletions qiskit/visualization/state_visualization.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2018.
# (C) Copyright IBM 2017, 2023.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
Expand All @@ -17,7 +17,7 @@
Visualization functions for quantum states.
"""

from typing import Optional, List, Union
from typing import List, Union
from functools import reduce
import colorsys

Expand All @@ -27,7 +27,6 @@
from qiskit.quantum_info.operators.operator import Operator
from qiskit.quantum_info.operators.symplectic import PauliList, SparsePauliOp
from qiskit.quantum_info.states.densitymatrix import DensityMatrix
from qiskit.utils.deprecation import deprecate_func
from qiskit.utils import optionals as _optionals
from qiskit.circuit.tools.pi_check import pi_check

Expand Down Expand Up @@ -1280,50 +1279,6 @@ def state_to_latex(
return prefix + latex_str + suffix


@deprecate_func(
additional_msg="For similar functionality, see sympy's ``nsimplify`` and ``latex`` functions.",
since="0.23.0",
package_name="qiskit-terra",
)
def num_to_latex_ket(raw_value: complex, first_term: bool, decimals: int = 10) -> Optional[str]:
"""Convert a complex number to latex code suitable for a ket expression
Args:
raw_value: Value to convert
first_term: If True then generate latex code for the first term in an expression
decimals: Number of decimal places to round to (default: 10).
Returns:
String with latex code or None if no term is required
"""
if np.around(np.abs(raw_value), decimals=decimals) == 0:
return None
return _num_to_latex(raw_value, first_term=first_term, decimals=decimals, coefficient=True)


@deprecate_func(
additional_msg="For similar functionality, see sympy's ``nsimplify`` and ``latex`` functions.",
since="0.23.0",
package_name="qiskit-terra",
)
def numbers_to_latex_terms(numbers: List[complex], decimals: int = 10) -> List[str]:
"""Convert a list of numbers to latex formatted terms
The first non-zero term is treated differently. For this term a leading + is suppressed.
Args:
numbers: List of numbers to format
decimals: Number of decimal places to round to (default: 10).
Returns:
List of formatted terms
"""
first_term = True
terms = []
for number in numbers:
term = num_to_latex_ket(number, first_term, decimals)
if term is not None:
first_term = False
terms.append(term)
return terms


def _numbers_to_latex_terms(numbers: List[complex], decimals: int = 10) -> List[str]:
"""Convert a list of numbers to latex formatted terms
Expand Down
16 changes: 16 additions & 0 deletions releasenotes/notes/add-filter-op-nodes-aa024a0f1058e4b7.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
features:
- |
Added a new transpiler pass :class:`.FilterOpNodes` which is used to filter
:class:`.DAGOpNode`\s in a :class:`.DAGCircuit`.
- |
Added a new keyword argument, ``label``, to the constructor on the
:class:`.BarrierBeforeFinalMeasurements` transpiler pass. If specified the
inserted barrier will be assigned the specified label. This also prevents
the inserted barrier from being merged with any any other pre-existing
adjacent barriers.
other:
- |
The preset pass managers used by :func:`.transpile` and returned with
:class:`.generate_preset_pass_manager` will no longer insert barriers
before final measurements in the output circuits.
4 changes: 4 additions & 0 deletions releasenotes/notes/fix_11143-d32a262538873a9d.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
upgrade:
- |
The functions ``qiskit.visualization.state_visualization.num_to_latex_ket`` and ``qiskit.visualization.state_visualization.numbers_to_latex_terms``
have been removed, as they were removed in Qiskit 0.40 (released in Jan 2023). For similar functionality, see sympy's ``nsimplify`` and ``latex`` functions.
32 changes: 32 additions & 0 deletions test/python/compiler/test_transpiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -1696,6 +1696,38 @@ def test_paulis_to_constrained_1q_basis(self, opt_level, basis):
self.assertGreaterEqual(set(basis) | {"barrier"}, transpiled.count_ops().keys())
self.assertEqual(Operator(qc), Operator(transpiled))

@data(0, 1, 2, 3)
def test_barrier_not_output(self, opt_level):
"""Test that barriers added as part internal transpiler operations do not leak out."""
qc = QuantumCircuit(2, 2)
qc.cx(0, 1)
qc.measure(range(2), range(2))
tqc = transpile(
qc,
initial_layout=[1, 4],
coupling_map=[[1, 2], [2, 3], [3, 4]],
optimization_level=opt_level,
)
self.assertNotIn("barrier", tqc.count_ops())

@data(0, 1, 2, 3)
def test_barrier_not_output_input_preservered(self, opt_level):
"""Test that barriers added as part internal transpiler operations do not leak out."""
qc = QuantumCircuit(2, 2)
qc.cx(0, 1)
qc.measure_all()
tqc = transpile(
qc,
initial_layout=[1, 4],
coupling_map=[[0, 1], [1, 2], [2, 3], [3, 4]],
optimization_level=opt_level,
)
op_counts = tqc.count_ops()
self.assertEqual(op_counts["barrier"], 1)
for inst in tqc.data:
if inst.operation.name == "barrier":
self.assertEqual(len(inst.qubits), 2)

@combine(opt_level=[0, 1, 2, 3])
def test_transpile_annotated_ops(self, opt_level):
"""Test transpilation of circuits with annotated operations."""
Expand Down
Loading

0 comments on commit 34e4a20

Please sign in to comment.