Skip to content

Commit

Permalink
Fix HighLevelSynthesis fast path
Browse files Browse the repository at this point in the history
  • Loading branch information
jakelishman committed Jul 29, 2024
1 parent 991263a commit f3a10a2
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 20 deletions.
17 changes: 16 additions & 1 deletion crates/circuit/src/circuit_instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ use pyo3::{intern, IntoPy, PyObject, PyResult};

use smallvec::SmallVec;

use crate::imports::{CONTROL_FLOW_OP, GATE, INSTRUCTION, OPERATION, WARNINGS_WARN};
use crate::imports::{
CONTROLLED_GATE, CONTROL_FLOW_OP, GATE, INSTRUCTION, OPERATION, WARNINGS_WARN,
};
use crate::operations::{
Operation, OperationRef, Param, PyGate, PyInstruction, PyOperation, StandardGate,
};
Expand Down Expand Up @@ -271,6 +273,19 @@ impl CircuitInstruction {
self.operation.try_standard_gate().is_some()
}

/// Is the :class:`.Operation` contained in this instruction a subclass of
/// :class:`.ControlledGate`?
pub fn is_controlled_gate(&self, py: Python) -> PyResult<bool> {
match self.operation.view() {
OperationRef::Standard(standard) => Ok(standard.num_ctrl_qubits() != 0),
OperationRef::Gate(gate) => gate
.gate
.bind(py)
.is_instance(CONTROLLED_GATE.get_bound(py)),
_ => Ok(false),
}
}

/// Is the :class:`.Operation` contained in this node a directive?
pub fn is_directive(&self) -> bool {
self.op().directive()
Expand Down
5 changes: 5 additions & 0 deletions crates/circuit/src/dag_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,11 @@ impl DAGOpNode {
self.instruction.is_standard_gate()
}

/// Is the :class:`.Operation` contained in this node a subclass of :class:`.ControlledGate`?
pub fn is_controlled_gate(&self, py: Python) -> PyResult<bool> {
self.instruction.is_controlled_gate(py)
}

/// Is the :class:`.Operation` contained in this node a directive?
pub fn is_directive(&self) -> bool {
self.instruction.is_directive()
Expand Down
64 changes: 45 additions & 19 deletions qiskit/transpiler/passes/synthesis/high_level_synthesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,10 @@
QFTSynthesisLine
"""

from typing import Optional, Union, List, Tuple, Callable
from __future__ import annotations

import typing
from typing import Optional, Union, List, Tuple, Callable, Sequence

import numpy as np
import rustworkx as rx
Expand All @@ -168,7 +171,7 @@
from qiskit.converters import circuit_to_dag, dag_to_circuit
from qiskit.transpiler.basepasses import TransformationPass
from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.circuit import ControlledGate, EquivalenceLibrary
from qiskit.circuit import ControlledGate, EquivalenceLibrary, equivalence
from qiskit.circuit.library import LinearFunction
from qiskit.transpiler.passes.utils import control_flow
from qiskit.transpiler.target import Target
Expand Down Expand Up @@ -210,6 +213,9 @@

from .plugin import HighLevelSynthesisPluginManager, HighLevelSynthesisPlugin

if typing.TYPE_CHECKING:
from qiskit.dagcircuit import DAGOpNode


class HLSConfig:
"""The high-level-synthesis config allows to specify a list of "methods" used by
Expand Down Expand Up @@ -396,6 +402,8 @@ def __init__(
if not self._top_level_only and (self._target is None or self._target.num_qubits is None):
basic_insts = {"measure", "reset", "barrier", "snapshot", "delay", "store"}
self._device_insts = basic_insts | set(self._basis_gates)
else:
self._device_insts = set()

def run(self, dag: DAGCircuit) -> DAGCircuit:
"""Run the HighLevelSynthesis pass on `dag`.
Expand Down Expand Up @@ -429,7 +437,7 @@ def run(self, dag: DAGCircuit) -> DAGCircuit:
[dag.find_bit(x).index for x in node.qargs] if self._use_qubit_indices else None
)

if self._definitely_skip_node(node):
if self._definitely_skip_node(node, qubits):
continue

decomposition, modified = self._recursively_handle_op(node.op, qubits)
Expand All @@ -448,16 +456,42 @@ def run(self, dag: DAGCircuit) -> DAGCircuit:

return dag

def _definitely_skip_node(self, node) -> bool:
def _definitely_skip_node(self, node: DAGOpNode, qubits: Sequence[int] | None) -> bool:
"""Fast-path determination of whether a node can certainly be skipped (i.e. nothing will
attempt to synthesise it).
attempt to synthesise it) without accessing its Python-space `Operation`.
This is tightly coupled to `_recursively_handle_op`; it exists as a temporary measure to
avoid Python-space `Operation` creation from a `DAGOpNode` if we wouldn't do anything to the
node (which is _most_ nodes)."""
# In general, Rust-space standard gates aren't going to need synthesising (but check to be
# sure).
return node.is_standard_gate() and not self._methods_to_try(node.name)
return (
# The fast path is just for Rust-space standard gates (which excludes
# `AnnotatedOperation`).
node.is_standard_gate()
# If it's a controlled gate, we might choose to do funny things to it.
and not node.is_controlled_gate()
# If there are plugins to try, they need to be tried.
and not self._methods_to_try(node.name)
# If all the above constraints hold, and it's already supported or the basis translator
# can handle it, we'll leave it be.
and (
self._instruction_supported(node.name, qubits)
# This uses unfortunately private details of `EquivalenceLibrary`, but so does the
# `BasisTranslator`, and this is supposed to just be temporary til this is moved
# into Rust space.
or (
self._equiv_lib is not None
and equivalence.Key(name=node.name, num_qubits=node.num_qubits)
in self._equiv_lib._key_to_node_index
)
)
)

def _instruction_supported(self, name: str, qubits: Sequence[int]) -> bool:
qubits = tuple(qubits) if qubits is not None else None
# include path for when target exists but target.num_qubits is None (BasicSimulator)
if self._target is None or self._target.num_qubits is None:
return name in self._device_insts
return self._target.instruction_supported(operation_name=name, qargs=qubits)

def _recursively_handle_op(
self, op: Operation, qubits: Optional[List] = None
Expand Down Expand Up @@ -507,17 +541,9 @@ def _recursively_handle_op(
# or is in equivalence library
controlled_gate_open_ctrl = isinstance(op, ControlledGate) and op._open_ctrl
if not controlled_gate_open_ctrl:
qargs = tuple(qubits) if qubits is not None else None
# include path for when target exists but target.num_qubits is None (BasicSimulator)
inst_supported = (
self._target.instruction_supported(
operation_name=op.name,
qargs=qargs,
)
if self._target is not None and self._target.num_qubits is not None
else op.name in self._device_insts
)
if inst_supported or (self._equiv_lib is not None and self._equiv_lib.has_entry(op)):
if self._instruction_supported(op.name, qubits) or (
self._equiv_lib is not None and self._equiv_lib.has_entry(op)
):
return op, False

try:
Expand Down
1 change: 1 addition & 0 deletions releasenotes/notes/avoid-op-creation-804c0bed6c408911.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ features_circuits:
These methods are:
* :meth:`.CircuitInstruction.is_standard_gate` and :meth:`.DAGOpNode.is_standard_gate`,
* :meth:`.CircuitInstruction.is_controlled_gate` and :meth:`.DAGOpNode.is_controlled_gate`,
* :meth:`.CircuitInstruction.is_directive` and :meth:`.DAGOpNode.is_directive`,
* :meth:`.CircuitInstruction.is_control_flow` and :meth:`.DAGOpNode.is_control_flow`, and
* :meth:`.CircuitInstruction.is_parameterized` and :meth:`.DAGOpNode.is_parameterized`.
Expand Down

0 comments on commit f3a10a2

Please sign in to comment.