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

Adds various convenience functions to qiskit #12470

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
4 changes: 2 additions & 2 deletions qiskit/circuit/library/blueprintcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,10 +190,10 @@ def count_ops(self):
self._build()
return super().count_ops()

def num_nonlocal_gates(self):
def num_nonlocal_gates(self, n=2):
sbrandhsn marked this conversation as resolved.
Show resolved Hide resolved
if not self._is_built:
self._build()
return super().num_nonlocal_gates()
return super().num_nonlocal_gates(n)

def num_connected_components(self, unitary_only=False):
if not self._is_built:
Expand Down
57 changes: 53 additions & 4 deletions qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -3386,6 +3386,50 @@ def depth(

return max(op_stack)

def depth_nq(self, n: int = 2) -> int:
"""Returns the depth in terms of quantum gates with at least n qubits (default n=2),
excluding directives.

Args:
n (int): The minimum number of qubits in an instruction that counts towards the depth.
Returns:
int: Quantum circuit depth in terms of gates with at least n qubits (default n=2).
"""
return self.depth(
lambda x: not getattr(x.operation, "_directive", False) and len(x.qubits) >= n
)

def num_nq_gates(self, n: int = 2) -> int:
"""Returns the number of gates with exactly n qubits (default: n=2), excluding directives.
See :meth:`.num_nonlocal_gates` to retrieve the number of gates with at least two-qubits
(excluding directives).

Args:
n (int): The exact number of qubits in an instruction.

Returns:
int: The number of gates in the quantum circuit with exactly n qubits (default n=2).
"""
return self.size(
lambda x: not getattr(x.operation, "_directive", False) and len(x.qubits) == n
)

def num_nonidle_qubits(self) -> int:
"""Returns the number of qubits in the quantum circuit that are non-idle,
i.e. are part of at least one gate.

Returns:
int: The number of non-idle qubits in the quantum circuit.
"""
return len(
{
qubit
for inst in self.data
for qubit in inst.qubits
if not getattr(inst.operation, "_directive", False)
}
)

def width(self) -> int:
"""Return number of qubits plus clbits in circuit.

Expand Down Expand Up @@ -3424,14 +3468,19 @@ def count_ops(self) -> "OrderedDict[Instruction, int]":
count_ops[instruction.operation.name] = count_ops.get(instruction.operation.name, 0) + 1
return OrderedDict(sorted(count_ops.items(), key=lambda kv: kv[1], reverse=True))

def num_nonlocal_gates(self) -> int:
"""Return number of non-local gates (i.e. involving 2+ qubits).
def num_nonlocal_gates(self, n: int = 2) -> int:
"""Returns the number of non-local gates (gates with at least n qubits), excluding directives and
including conditional gates. By default, returns the number of non-local gates with at least two
qubits.

Conditional nonlocal gates are also included.
Args:
n (int): The minimum number of qubits in a gate to count towards the returned quantity.
Returns:
int: Number of gates in the quantum circuit with at least n qubits (default n=2).
"""
multi_qubit_gates = 0
for instruction in self._data:
if instruction.operation.num_qubits > 1 and not getattr(
if instruction.operation.num_qubits >= n and not getattr(
instruction.operation, "_directive", False
):
multi_qubit_gates += 1
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
features:
- |
Added convenience methods to the :class:`.QuantumCircuit` class that return the depth in terms of gates that have at least n-qubits (:meth:`.QuantumCircuit.depth_nq`).
Further convenience methods were added for determining the number of n-qubit gates (:meth:`.QuantumCircuit.num_nq_gates`).
Both of these methods assume a default of n=2, i.e. the depth in terms of two-qubit gates and the number of two-qubit gates excluding directives are returned.
Another method was added that returns the number of non-idle qubits in the quantum circuit, i.e. qubits that are part of at least one gate (:meth:`.QuantumCircuit.num_nonidle_qubits`).
104 changes: 103 additions & 1 deletion test/python/circuit/test_circuit_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@
import unittest
import numpy as np

from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit, pulse
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit, pulse, transpile
from qiskit.circuit import Clbit
from qiskit.circuit.library import RXGate, RYGate
from qiskit.circuit.exceptions import CircuitError
from qiskit.providers.fake_provider import GenericBackendV2
from test import QiskitTestCase # pylint: disable=wrong-import-order


Expand Down Expand Up @@ -671,6 +672,107 @@ def test_circuit_size_2qubit(self):
qc.rzz(0.1, q[1], q[2])
self.assertEqual(qc.size(lambda x: x.operation.num_qubits == 2), 2)

def test_circuit_depth_nq(self):
"""Test depth_nq where the depth of a circuit is incremented for every n-qubit gate in
the critical path."""
qasmstr = """OPENQASM 2.0;
include "qelib1.inc";
gate iswap q0,q1 { s q0; s q1; h q0; cx q0,q1; cx q1,q0; h q1; }
gate cs q0,q1 { p(pi/4) q0; cx q0,q1; p(-pi/4) q1; cx q0,q1; p(pi/4) q1; }
gate ryy(param0) q0,q1 { rx(pi/2) q0; rx(pi/2) q1; cx q0,q1; rz(param0) q1; cx q0,q1; rx(-pi/2) q0; rx(-pi/2) q1; }
gate ccz q0,q1,q2 { h q2; ccx q0,q1,q2; h q2; }
gate r(param0,param1) q0 { u3(param0,param1 - pi/2,pi/2 - 1.0*param1) q0; }
qreg q[10];
ccx q[3],q[6],q[4];
iswap q[9],q[5];
cs q[1],q[2];
swap q[0],q[8];
rz(5.946819885446965) q[7];
cs q[5],q[6];
ryy(1.5599841893903141) q[8],q[7];
cx q[9],q[4];
ccx q[3],q[2],q[1];
s q[0];
ryy(3.473045854916819) q[2],q[9];
c3sqrtx q[6],q[5],q[7],q[1];
u(3.441311875095991,0.3238280161025424,4.644717266292606) q[3];
h q[0];
ryy(2.0150270151373633) q[4],q[8];
cp(5.8338842752808135) q[2],q[9];
rxx(5.9420872737457495) q[1],q[6];
p(5.496968207669997) q[3];
ccz q[4],q[5],q[0];
cu3(2.335105827938611,1.6824489962853504,3.328906865580425) q[7],q[8];
ryy(1.1763948000447193) q[1],q[3];
cx q[6],q[8];
r(4.971911764092657,1.0148707128418426) q[7];
z q[5];
x q[4];
h q[2];
y q[9];
r(2.0146429536861477,4.187259749115217) q[0];"""

qc = QuantumCircuit.from_qasm_str(qasmstr)
expected_depth_nq = {0: qc.depth(), 1: qc.depth(), 2: 5, 3: 4, 4: 1, 5: 0}
depth_nq = {i: qc.depth_nq(n=i) for i in range(0, 6)}
self.assertEqual(depth_nq, expected_depth_nq)

def test_circuit_num_nq_gates(self):
"""Test num_nq_gates which returns the number of n-qubit gates."""
qasmstr = """OPENQASM 2.0;
include "qelib1.inc";
gate iswap q0,q1 { s q0; s q1; h q0; cx q0,q1; cx q1,q0; h q1; }
gate cs q0,q1 { p(pi/4) q0; cx q0,q1; p(-pi/4) q1; cx q0,q1; p(pi/4) q1; }
gate ryy(param0) q0,q1 { rx(pi/2) q0; rx(pi/2) q1; cx q0,q1; rz(param0) q1; cx q0,q1; rx(-pi/2) q0; rx(-pi/2) q1; }
gate ccz q0,q1,q2 { h q2; ccx q0,q1,q2; h q2; }
gate r(param0,param1) q0 { u3(param0,param1 - pi/2,pi/2 - 1.0*param1) q0; }
qreg q[10];
ccx q[3],q[6],q[4];
iswap q[9],q[5];
cs q[1],q[2];
swap q[0],q[8];
rz(5.946819885446965) q[7];
cs q[5],q[6];
ryy(1.5599841893903141) q[8],q[7];
cx q[9],q[4];
ccx q[3],q[2],q[1];
s q[0];
ryy(3.473045854916819) q[2],q[9];
c3sqrtx q[6],q[5],q[7],q[1];
u(3.441311875095991,0.3238280161025424,4.644717266292606) q[3];
h q[0];
ryy(2.0150270151373633) q[4],q[8];
cp(5.8338842752808135) q[2],q[9];
rxx(5.9420872737457495) q[1],q[6];
p(5.496968207669997) q[3];
ccz q[4],q[5],q[0];
cu3(2.335105827938611,1.6824489962853504,3.328906865580425) q[7],q[8];
ryy(1.1763948000447193) q[1],q[3];
cx q[6],q[8];
r(4.971911764092657,1.0148707128418426) q[7];
z q[5];
x q[4];
h q[2];
y q[9];
r(2.0146429536861477,4.187259749115217) q[0];"""
expected_n_nq_gates = {0: 0, 1: 11, 2: 13, 3: 3, 4: 1, 5: 0}
qc = QuantumCircuit.from_qasm_str(qasmstr)
n_nq_gates = {i: qc.num_nq_gates(n=i) for i in range(0, 6)}
self.assertEqual(n_nq_gates, expected_n_nq_gates)

def test_circuit_nonidle_qubits(self):
"""Test that the number of nonidle qubits is correctly returned."""
qc = QuantumCircuit(5)
qc.h(0)
qc.cx(0, 1)
qc.cx(1, 2)
qc.barrier()
qc.cx(2, 3)
qc.cx(3, 4)

qc_t = transpile(qc, backend=GenericBackendV2(13))
self.assertEqual(qc_t.num_nonidle_qubits(), 5)

def test_circuit_count_ops(self):
"""Test circuit count ops."""
q = QuantumRegister(6, "q")
Expand Down
Loading