Skip to content

Commit 9040bb9

Browse files
kdkmtreinish
andauthored
API changes for optional registers in QuantumCircuit. (#5486)
* Allow creation of Bits without requiring an associated Register. * Add tests covering change in Bit.__eq__ behavior. * Register.__init__ to optionally accept list of Bits. * QuantumCircuit.__init__ to optionally accept a list of Bits. * Add DAGCircuit.add_{qubit,clbit}s methods. * Update qiskit/circuit/bit.py Co-authored-by: Matthew Treinish <mtreinish@kortar.org>
1 parent 5a62c21 commit 9040bb9

File tree

11 files changed

+454
-40
lines changed

11 files changed

+454
-40
lines changed

qiskit/circuit/bit.py

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,25 +21,32 @@ class Bit:
2121

2222
__slots__ = {'_register', '_index', '_hash', '_repr'}
2323

24-
def __init__(self, register, index):
24+
def __init__(self, register=None, index=None):
2525
"""Create a new generic bit.
2626
"""
27-
try:
28-
index = int(index)
29-
except Exception:
30-
raise CircuitError("index needs to be castable to an int: type %s was provided" %
31-
type(index))
32-
33-
if index < 0:
34-
index += register.size
35-
36-
if index >= register.size:
37-
raise CircuitError("index must be under the size of the register: %s was provided" %
38-
index)
39-
40-
self._register = register
41-
self._index = index
42-
self._update_hash()
27+
if (register, index) == (None, None):
28+
self._register = None
29+
self._index = None
30+
# To sidestep the overridden Bit.__hash__ and use the default hash
31+
# algorithm (only new-style Bits), call default object hash method.
32+
self._hash = object.__hash__(self)
33+
else:
34+
try:
35+
index = int(index)
36+
except Exception:
37+
raise CircuitError("index needs to be castable to an int: type %s was provided" %
38+
type(index))
39+
40+
if index < 0:
41+
index += register.size
42+
43+
if index >= register.size:
44+
raise CircuitError("index must be under the size of the register: %s was provided" %
45+
index)
46+
47+
self._register = register
48+
self._index = index
49+
self._update_hash()
4350

4451
def _update_hash(self):
4552
self._hash = hash((self._register, self._index))
@@ -49,33 +56,51 @@ def _update_hash(self):
4956
@property
5057
def register(self):
5158
"""Get bit's register."""
59+
if (self._register, self._index) == (None, None):
60+
raise CircuitError('Attmped to query register of a new-style Bit.')
61+
5262
return self._register
5363

5464
@register.setter
5565
def register(self, value):
5666
"""Set bit's register."""
67+
if (self._register, self._index) == (None, None):
68+
raise CircuitError('Attmped to set register of a new-style Bit.')
69+
5770
self._register = value
5871
self._update_hash()
5972

6073
@property
6174
def index(self):
6275
"""Get bit's index."""
76+
if (self._register, self._index) == (None, None):
77+
raise CircuitError('Attmped to query index of a new-style Bit.')
78+
6379
return self._index
6480

6581
@index.setter
6682
def index(self, value):
6783
"""Set bit's index."""
84+
if (self._register, self._index) == (None, None):
85+
raise CircuitError('Attmped to set index of a new-style Bit.')
86+
6887
self._index = value
6988
self._update_hash()
7089

7190
def __repr__(self):
7291
"""Return the official string representing the bit."""
92+
if (self._register, self._index) == (None, None):
93+
# Similar to __hash__, use default repr method for new-style Bits.
94+
return object.__repr__(self)
7395
return self._repr
7496

7597
def __hash__(self):
7698
return self._hash
7799

78100
def __eq__(self, other):
101+
if (self._register, self._index) == (None, None):
102+
return other is self
103+
79104
try:
80105
return self._repr == other._repr
81106
except AttributeError:

qiskit/circuit/classicalregister.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,18 @@
2323
class Clbit(Bit):
2424
"""Implement a classical bit."""
2525

26-
def __init__(self, register, index):
26+
def __init__(self, register=None, index=None):
2727
"""Creates a classical bit.
2828
2929
Args:
30-
register (ClassicalRegister): a classical register.
31-
index (int): the index to insert the bit
30+
register (ClassicalRegister): Optional. A classical register containing the bit.
31+
index (int): Optional. The index of the bit in its containing register.
3232
3333
Raises:
3434
CircuitError: if the provided register is not a valid :class:`ClassicalRegister`
3535
"""
3636

37-
if isinstance(register, ClassicalRegister):
37+
if register is None or isinstance(register, ClassicalRegister):
3838
super().__init__(register, index)
3939
else:
4040
raise CircuitError('Clbit needs a ClassicalRegister and %s was provided' %

qiskit/circuit/quantumcircuit.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
from qiskit.circuit.exceptions import CircuitError
3434
from qiskit.utils.deprecation import deprecate_function
3535
from .parameterexpression import ParameterExpression
36-
from .quantumregister import QuantumRegister, Qubit, AncillaRegister
36+
from .quantumregister import QuantumRegister, Qubit, AncillaRegister, AncillaQubit
3737
from .classicalregister import ClassicalRegister, Clbit
3838
from .parametertable import ParameterTable
3939
from .parametervector import ParameterVector
@@ -59,8 +59,8 @@ class QuantumCircuit:
5959
A circuit is a list of instructions bound to some registers.
6060
6161
Args:
62-
regs (list(:class:`Register`) or list(``int``)): The registers to be
63-
included in the circuit.
62+
regs (list(:class:`Register`) or list(``int``) or list(list(:class:`Bit`))): The
63+
registers to be included in the circuit.
6464
6565
* If a list of :class:`Register` objects, represents the :class:`QuantumRegister`
6666
and/or :class:`ClassicalRegister` objects to include in the circuit.
@@ -80,6 +80,9 @@ class QuantumCircuit:
8080
* ``QuantumCircuit(4) # A QuantumCircuit with 4 qubits``
8181
* ``QuantumCircuit(4, 3) # A QuantumCircuit with 4 qubits and 3 classical bits``
8282
83+
* If a list of python lists containing :class:`Bit` objects, a collection of
84+
:class:`Bit` s to be added to the circuit.
85+
8386
8487
name (str): the name of the quantum circuit. If not set, an
8588
automatically generated string will be assigned.
@@ -146,7 +149,7 @@ class QuantumCircuit:
146149
extension_lib = "include \"qelib1.inc\";"
147150

148151
def __init__(self, *regs, name=None, global_phase=0, metadata=None):
149-
if any([not isinstance(reg, (QuantumRegister, ClassicalRegister)) for reg in regs]):
152+
if any([not isinstance(reg, (list, QuantumRegister, ClassicalRegister)) for reg in regs]):
150153
# check if inputs are integers, but also allow e.g. 2.0
151154

152155
try:
@@ -1012,7 +1015,10 @@ def add_register(self, *regs):
10121015
" with %s." % (regs,))
10131016

10141017
for register in regs:
1015-
if register.name in [reg.name for reg in self.qregs + self.cregs]:
1018+
if (
1019+
isinstance(register, Register)
1020+
and any(register.name == reg.name for reg in self.qregs + self.cregs)
1021+
):
10161022
raise CircuitError("register name \"%s\" already exists"
10171023
% register.name)
10181024

@@ -1025,9 +1031,29 @@ def add_register(self, *regs):
10251031
elif isinstance(register, ClassicalRegister):
10261032
self.cregs.append(register)
10271033
self._clbits.extend(register)
1034+
elif isinstance(register, list):
1035+
self.add_bits(register)
10281036
else:
10291037
raise CircuitError("expected a register")
10301038

1039+
def add_bits(self, bits):
1040+
"""Add Bits to the circuit."""
1041+
duplicate_bits = set(self.qubits + self.clbits).intersection(bits)
1042+
if duplicate_bits:
1043+
raise CircuitError("Attempted to add bits found already in circuit: "
1044+
"{}".format(duplicate_bits))
1045+
1046+
for bit in bits:
1047+
if isinstance(bit, AncillaQubit):
1048+
self._ancillas.append(bit)
1049+
elif isinstance(bit, Qubit):
1050+
self._qubits.append(bit)
1051+
elif isinstance(bit, Clbit):
1052+
self._clbits.append(bit)
1053+
else:
1054+
raise CircuitError("Expected an instance of Qubit, Clbit, or "
1055+
"AncillaQubit, but was passed {}".format(bit))
1056+
10311057
def _check_dups(self, qubits):
10321058
"""Raise exception if list of qubits contains duplicates."""
10331059
squbits = set(qubits)

qiskit/circuit/quantumregister.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,18 @@
2323
class Qubit(Bit):
2424
"""Implement a quantum bit."""
2525

26-
def __init__(self, register, index):
26+
def __init__(self, register=None, index=None):
2727
"""Creates a qubit.
2828
2929
Args:
30-
register (QuantumRegister): a quantum register.
31-
index (int): the index to insert the qubit
30+
register (QuantumRegister): Optional. A quantum register containing the bit.
31+
index (int): Optional. The index of the bit in its containing register.
3232
3333
Raises:
3434
CircuitError: if the provided register is not a valid :class:`QuantumRegister`
3535
"""
3636

37-
if isinstance(register, QuantumRegister):
37+
if register is None or isinstance(register, QuantumRegister):
3838
super().__init__(register, index)
3939
else:
4040
raise CircuitError('Qubit needs a QuantumRegister and %s was provided' %

qiskit/circuit/register.py

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,41 @@ class Register:
3737
prefix = 'reg'
3838
bit_type = None
3939

40-
def __init__(self, size, name=None):
40+
def __init__(self, size=None, name=None, bits=None):
4141
"""Create a new generic register.
42+
43+
Either the ``size`` or the ``bits`` argument must be provided. If
44+
``size`` is not None, the register will be pre-populated with bits of the
45+
correct type.
46+
47+
Args:
48+
size (int): Optional. The number of bits to include in the register.
49+
name (str): Optional. The name of the register. If not provided, a
50+
unique name will be auto-generated from the register type.
51+
bits (list[Bit]): Optional. A list of Bit() instances to be used to
52+
populate the register.
53+
54+
Raises:
55+
CircuitError: if both the ``size`` and ``bits`` arguments are
56+
provided, or if neither are.
57+
CircuitError: if ``size`` is not valid.
58+
CircuitError: if ``name`` is not a valid name according to the
59+
OpenQASM spec.
60+
CircuitError: if ``bits`` contained bits of an incorrect type.
4261
"""
4362

63+
if (
64+
(size, bits) == (None, None)
65+
or (size is not None and bits is not None)
66+
):
67+
raise CircuitError("Exactly one of the size or bits arguments can be "
68+
"provided. Provided size=%s bits=%s."
69+
% (size, bits))
70+
4471
# validate (or cast) size
72+
if bits is not None:
73+
size = len(bits)
74+
4575
try:
4676
valid_size = size == int(size)
4777
except (ValueError, TypeError):
@@ -74,7 +104,13 @@ def __init__(self, size, name=None):
74104

75105
self._hash = hash((type(self), self._name, self._size))
76106
self._repr = "%s(%d, '%s')" % (self.__class__.__qualname__, self.size, self.name)
77-
self._bits = [self.bit_type(self, idx) for idx in range(size)]
107+
if bits is not None:
108+
if any(not isinstance(bit, self.bit_type) for bit in bits):
109+
raise CircuitError("Provided bits did not all match "
110+
"register type. bits=%s" % bits)
111+
self._bits = list(bits)
112+
else:
113+
self._bits = [self.bit_type(self, idx) for idx in range(size)]
78114

79115
def _update_bits_hash(self):
80116
for bit in self._bits:
@@ -149,18 +185,33 @@ def __iter__(self):
149185

150186
def __eq__(self, other):
151187
"""Two Registers are the same if they are of the same type
152-
(i.e. quantum/classical), and have the same name and size.
188+
(i.e. quantum/classical), and have the same name and size. Additionally,
189+
if either Register contains new-style bits, the bits in both registers
190+
will be checked for pairwise equality. If two registers are equal,
191+
they will have behave identically when specified as circuit args.
153192
154193
Args:
155194
other (Register): other Register
156195
157196
Returns:
158197
bool: `self` and `other` are equal.
159198
"""
160-
try:
161-
return self._repr == other._repr
162-
except AttributeError:
163-
return False
199+
if self is other:
200+
return True
201+
202+
res = False
203+
if type(self) is type(other) and \
204+
self._repr == other._repr and \
205+
all(
206+
# For new-style bits, check bitwise equality.
207+
sbit == obit
208+
for sbit, obit in zip(self, other)
209+
if None in
210+
(sbit._register, sbit._index,
211+
obit._register, obit._index)
212+
):
213+
res = True
214+
return res
164215

165216
def __hash__(self):
166217
"""Make object hashable, based on the name and size to hash."""

qiskit/dagcircuit/dagcircuit.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@
3030
import retworkx as rx
3131

3232
from qiskit.circuit.quantumregister import QuantumRegister, Qubit
33-
from qiskit.circuit.classicalregister import ClassicalRegister
33+
from qiskit.circuit.classicalregister import ClassicalRegister, Clbit
3434
from qiskit.circuit.gate import Gate
35+
from qiskit.circuit.exceptions import CircuitError
3536
from qiskit.circuit.parameterexpression import ParameterExpression
3637
from qiskit.dagcircuit.exceptions import DAGCircuitError
3738
from qiskit.dagcircuit.dagnode import DAGNode
@@ -233,6 +234,32 @@ def remove_all_ops_named(self, opname):
233234
for n in self.named_nodes(opname):
234235
self.remove_op_node(n)
235236

237+
def add_qubits(self, qubits):
238+
"""Add individual qubit wires."""
239+
if any(not isinstance(qubit, Qubit) for qubit in qubits):
240+
raise DAGCircuitError("not a Qubit instance.")
241+
242+
duplicate_qubits = set(self.qubits).intersection(qubits)
243+
if duplicate_qubits:
244+
raise DAGCircuitError("duplicate qubits %s" % duplicate_qubits)
245+
246+
self.qubits.extend(qubits)
247+
for qubit in qubits:
248+
self._add_wire(qubit)
249+
250+
def add_clbits(self, clbits):
251+
"""Add individual clbit wires."""
252+
if any(not isinstance(clbit, Clbit) for clbit in clbits):
253+
raise DAGCircuitError("not a Clbit instance.")
254+
255+
duplicate_clbits = set(self.clbits).intersection(clbits)
256+
if duplicate_clbits:
257+
raise DAGCircuitError("duplicate clbits %s" % duplicate_clbits)
258+
259+
self.clbits.extend(clbits)
260+
for clbit in clbits:
261+
self._add_wire(clbit)
262+
236263
def add_qreg(self, qreg):
237264
"""Add all wires in a quantum register."""
238265
if not isinstance(qreg, QuantumRegister):
@@ -268,7 +295,10 @@ def _add_wire(self, wire):
268295
if wire not in self._wires:
269296
self._wires.add(wire)
270297

271-
wire_name = "%s[%s]" % (wire.register.name, wire.index)
298+
try:
299+
wire_name = "%s[%s]" % (wire.register.name, wire.index)
300+
except CircuitError:
301+
wire_name = "%s" % wire
272302

273303
inp_node = DAGNode(type='in', name=wire_name, wire=wire)
274304
outp_node = DAGNode(type='out', name=wire_name, wire=wire)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
features:
3+
- |
4+
The constructors of the :class:`~qiskit.circuit.Bit` class and subclasses,
5+
:class:`~qiskit.circuit.Qubit`, :class:`~qiskit.circuit.Clbit`, and
6+
:class:`~qiskit.circuit.AncillaQubit`, have been updated such that their
7+
two parameters, ``register`` and ``index`` are now optional.

0 commit comments

Comments
 (0)