Skip to content

Commit 9fe60cb

Browse files
kdkmergify[bot]
andauthored
Add library of gates with their equivalent circuit implementations. (#3946)
* Add library of gates with their equivalent circuit implementations. * Document equivalent circuit return order. Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
1 parent fb2a497 commit 9fe60cb

File tree

4 files changed

+535
-0
lines changed

4 files changed

+535
-0
lines changed

qiskit/circuit/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
Reset
4444
Instruction
4545
InstructionSet
46+
EquivalenceLibrary
4647
4748
Parametric Quantum Circuits
4849
===========================
@@ -75,3 +76,4 @@
7576
from .parameter import Parameter
7677
from .parametervector import ParameterVector
7778
from .parameterexpression import ParameterExpression
79+
from .equivalence import EquivalenceLibrary

qiskit/circuit/equivalence.py

+184
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
# -*- coding: utf-8 -*-
2+
3+
# This code is part of Qiskit.
4+
#
5+
# (C) Copyright IBM 2020.
6+
#
7+
# This code is licensed under the Apache License, Version 2.0. You may
8+
# obtain a copy of this license in the LICENSE.txt file in the root directory
9+
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
10+
#
11+
# Any modifications or derivative works of this code must retain this
12+
# copyright notice, and modified files need to carry a notice indicating
13+
# that they have been altered from the originals.
14+
15+
"""Gate equivalence library."""
16+
17+
from collections import namedtuple
18+
19+
from .exceptions import CircuitError
20+
from .parameterexpression import ParameterExpression
21+
22+
Key = namedtuple('Key', ['name',
23+
'num_qubits'])
24+
25+
Entry = namedtuple('Entry', ['search_base',
26+
'equivalences'])
27+
28+
Equivalence = namedtuple('Equivalence', ['params', # Ordered to match Gate.params
29+
'circuit'])
30+
31+
32+
class EquivalenceLibrary():
33+
"""A library providing a one-way mapping of Gates to their equivalent
34+
implementations as QuantumCircuits."""
35+
36+
def __init__(self, *, base=None):
37+
"""Create a new equivalence library.
38+
39+
Args:
40+
base (Optional[EquivalenceLibrary]): Base equivalence library to
41+
will be referenced if an entry is not found in this library.
42+
"""
43+
self._base = base
44+
45+
self._map = {}
46+
47+
def add_equivalence(self, gate, equivalent_circuit):
48+
"""Add a new equivalence to the library. Future queries for the Gate
49+
will include the given circuit, in addition to all existing equivalences
50+
(including those from base).
51+
52+
Parameterized Gates (those including `qiskit.circuit.Parameters` in their
53+
`Gate.params`) can be marked equivalent to parameterized circuits,
54+
provided the parameters match.
55+
56+
Args:
57+
gate (Gate): A Gate instance.
58+
equivalent_circuit (QuantumCircuit): A circuit equivalently
59+
implementing the given Gate.
60+
"""
61+
62+
_raise_if_shape_mismatch(gate, equivalent_circuit)
63+
_raise_if_param_mismatch(gate.params, equivalent_circuit.parameters)
64+
65+
key = Key(name=gate.name,
66+
num_qubits=gate.num_qubits)
67+
68+
equiv = Equivalence(params=gate.params.copy(),
69+
circuit=equivalent_circuit.copy())
70+
71+
if key not in self._map:
72+
self._map[key] = Entry(search_base=True, equivalences=[])
73+
74+
self._map[key].equivalences.append(equiv)
75+
76+
def set_entry(self, gate, entry):
77+
"""Set the equivalence record for a Gate. Future queries for the Gate
78+
will return only the circuits provided.
79+
80+
Parameterized Gates (those including `qiskit.circuit.Parameters` in their
81+
`Gate.params`) can be marked equivalent to parameterized circuits,
82+
provided the parameters match.
83+
84+
Args:
85+
gate (Gate): A Gate instance.
86+
entry (List['QuantumCircuit']) : A list of QuantumCircuits, each
87+
equivalently implementing the given Gate.
88+
"""
89+
90+
for equiv in entry:
91+
_raise_if_shape_mismatch(gate, equiv)
92+
_raise_if_param_mismatch(gate.params, equiv.parameters)
93+
94+
key = Key(name=gate.name,
95+
num_qubits=gate.num_qubits)
96+
97+
equivs = [Equivalence(params=gate.params.copy(),
98+
circuit=equiv.copy())
99+
for equiv in entry]
100+
101+
self._map[key] = Entry(search_base=False,
102+
equivalences=equivs)
103+
104+
def get_entry(self, gate):
105+
"""Gets the set of QuantumCircuits circuits from the library which
106+
equivalently implement the given Gate.
107+
108+
Parameterized circuits will have their parameters replaced with the
109+
corresponding entries from Gate.params.
110+
111+
Args:
112+
gate (Gate) - Gate: A Gate instance.
113+
114+
Returns:
115+
List[QuantumCircuit]: A list of equivalent QuantumCircuits. If empty,
116+
library contains no known decompositions of Gate.
117+
118+
Returned circuits will be ordered according to their insertion in
119+
the library, from earliest to latest, from top to base. The
120+
ordering of the StandardEquivalenceLibrary will not generally be
121+
consistent across Qiskit versions.
122+
"""
123+
124+
key = Key(name=gate.name,
125+
num_qubits=gate.num_qubits)
126+
127+
query_params = gate.params
128+
129+
if key in self._map:
130+
entry = self._map[key]
131+
search_base, equivs = entry
132+
133+
rtn = [_rebind_equiv(equiv, query_params) for equiv in equivs]
134+
135+
if search_base and self._base is not None:
136+
return rtn + self._base.get_entry(gate)
137+
return rtn
138+
139+
if self._base is None:
140+
return []
141+
142+
return self._base.get_entry(gate)
143+
144+
145+
def _raise_if_param_mismatch(gate_params, circuit_parameters):
146+
gate_parameters = [p for p in gate_params
147+
if isinstance(p, ParameterExpression)]
148+
149+
if set(gate_parameters) != circuit_parameters:
150+
raise CircuitError('Cannot add equivalence between circuit and gate '
151+
'of different parameters. Gate params: {}. '
152+
'Circuit params: {}.'.format(
153+
gate_parameters,
154+
circuit_parameters))
155+
156+
157+
def _raise_if_shape_mismatch(gate, circuit):
158+
if (gate.num_qubits != circuit.n_qubits
159+
or gate.num_clbits != circuit.n_clbits):
160+
raise CircuitError('Cannot add equivalence between circuit and gate '
161+
'of different shapes. Gate: {} qubits and {} clbits. '
162+
'Circuit: {} qubits and {} clbits.'.format(
163+
gate.num_qubits, gate.num_clbits,
164+
circuit.n_qubits, circuit.n_clbits))
165+
166+
167+
def _rebind_equiv(equiv, query_params):
168+
equiv_params, equiv_circuit = equiv
169+
170+
param_map = dict(zip(equiv_params, query_params))
171+
172+
symbolic_param_map, numeric_param_map = _partition_dict(
173+
param_map,
174+
lambda k, v: isinstance(v, ParameterExpression))
175+
176+
equiv = equiv_circuit.bind_parameters(numeric_param_map)
177+
equiv._substitute_parameters(symbolic_param_map)
178+
179+
return equiv
180+
181+
182+
def _partition_dict(dict_, predicate):
183+
return ({k: v for k, v in dict_.items() if predicate(k, v)},
184+
{k: v for k, v in dict_.items() if not predicate(k, v)})

qiskit/circuit/quantumcircuit.py

+7
Original file line numberDiff line numberDiff line change
@@ -969,6 +969,13 @@ def n_qubits(self):
969969
qubits += reg.size
970970
return qubits
971971

972+
@property
973+
def n_clbits(self):
974+
"""
975+
Return number of classical bits.
976+
"""
977+
return sum(len(reg) for reg in self.cregs)
978+
972979
def count_ops(self):
973980
"""Count each operation kind in the circuit.
974981

0 commit comments

Comments
 (0)