Repository: Qiskit/qiskit-terra
Pull request #5486 (2020-12-08 15:43:38)

API changes for optional registers in QuantumCircuit.
<!--
⚠️ If you do not respect this template, your pull request will be closed.
⚠️ Your pull request title should be short detailed and understandable for all.
⚠️ Also, please add a release note file using reno if the change needs to be
  documented in the release notes.
⚠️ If your pull request fixes an open issue, please link to the issue.

- [ ] I have added the tests to cover my changes.
- [ ] I have updated the documentation accordingly.
- [ ] I have read the CONTRIBUTING document.
-->

### Summary

Updates to `Bit`, `Register`, `QuantumCircuit` and `DAGCircuit` classes to move from `Registers` as the core data structure unit toward making `Bit`s fundamental and `Register`s optional.

### Details and comments

This PR covers only the API changes required to create and add `Bit`s to `QuantumCircuit`s, `DAGCircuit`s and `Register`s. Subsequent PRs:
- Avoiding `Bit.register` and `.index` internally in `QuantumCircuit` and `DAGCircuit` (and thus supporting `Register`s as optional)
- Updates as above, for quantum_info, scheduler, and assembler
- Updates as above, for all transpiler passes
- Updates as above, for visualization tools
- Updates as above, for circuit library
- Deprecate `Bit.index` and `Bit.register` properties
- Deprecate creation of implicit `'q'`/`'c'` registers from `QuantumCircuit` int constructor

See working branch https://github.com/Qiskit/qiskit-terra/compare/master...kdk:quantumcircuit-optional-registers .


kdk: > So I think this looks good and I like the direction. Just a couple of questions below and a few inline comments/questions:
> 
>     1. For a register-free bit it basically is just a type/class and a hash/id for the object? Longer term It does make we wonder if we just want to use int indices for bits in QuantumCircuit/DAGCircuit and registers are lists of ints. (ie basically metadata around the bits in a circuit). But that would be a much larger change and probably too hard to make. (this is more just an idle thought, I do think the direction in this PR makes the most sense)

That's right. Long term, converting to `int`s is something to consider, but would be a larger change (and might have some design questions around if `Bit`s are implicitly ordered or if their order is governed by the circuit).

>     2. Have you done any performance testing on a registerless circuit vs a register one? I know that we've spent a bunch of time tuning the register <-> bit creation and comparisons as operations with bits end up being a lot of the checks we do in the transpiler. Looking at the new bit classes and how `__repr__` is changed I think we should look into this ahead of time.

Agree, I've done some preliminary testing on the development branch of the final state (all converted to new-style Bits) but only to check the overall design for large regressions and not covering the intermediate state where we need to check and toggle between old- and new-style (which I would expect to be slower, but not substantially).



Patch:
diff --git a/qiskit/circuit/bit.py b/qiskit/circuit/bit.py
index f4c501f55..81dc5082a 100644
--- a/qiskit/circuit/bit.py
+++ b/qiskit/circuit/bit.py
@@ -21,25 +21,32 @@ class Bit:
 
     __slots__ = {'_register', '_index', '_hash', '_repr'}
 
-    def __init__(self, register, index):
+    def __init__(self, register=None, index=None):
         """Create a new generic bit.
         """
-        try:
-            index = int(index)
-        except Exception:
-            raise CircuitError("index needs to be castable to an int: type %s was provided" %
-                               type(index))
-
-        if index < 0:
-            index += register.size
-
-        if index >= register.size:
-            raise CircuitError("index must be under the size of the register: %s was provided" %
-                               index)
-
-        self._register = register
-        self._index = index
-        self._update_hash()
+        if (register, index) == (None, None):
+            self._register = None
+            self._index = None
+            # To sidestep the overridden Bit.__hash__ and use the default hash
+            # algorithm (only new-style Bits), call default object hash method.
+            self._hash = object.__hash__(self)
+        else:
+            try:
+                index = int(index)
+            except Exception:
+                raise CircuitError("index needs to be castable to an int: type %s was provided" %
+                                   type(index))
+
+            if index < 0:
+                index += register.size
+
+            if index >= register.size:
+                raise CircuitError("index must be under the size of the register: %s was provided" %
+                                   index)
+
+            self._register = register
+            self._index = index
+            self._update_hash()
 
     def _update_hash(self):
         self._hash = hash((self._register, self._index))
@@ -49,33 +56,51 @@ class Bit:
     @property
     def register(self):
         """Get bit's register."""
+        if (self._register, self._index) == (None, None):
+            raise CircuitError('Attmped to query register of a new-style Bit.')
+
         return self._register
 
     @register.setter
     def register(self, value):
         """Set bit's register."""
+        if (self._register, self._index) == (None, None):
+            raise CircuitError('Attmped to set register of a new-style Bit.')
+
         self._register = value
         self._update_hash()
 
     @property
     def index(self):
         """Get bit's index."""
+        if (self._register, self._index) == (None, None):
+            raise CircuitError('Attmped to query index of a new-style Bit.')
+
         return self._index
 
     @index.setter
     def index(self, value):
         """Set bit's index."""
+        if (self._register, self._index) == (None, None):
+            raise CircuitError('Attmped to set index of a new-style Bit.')
+
         self._index = value
         self._update_hash()
 
     def __repr__(self):
         """Return the official string representing the bit."""
+        if (self._register, self._index) == (None, None):
+            # Similar to __hash__, use default repr method for new-style Bits.
+            return object.__repr__(self)
         return self._repr
 
     def __hash__(self):
         return self._hash
 
     def __eq__(self, other):
+        if (self._register, self._index) == (None, None):
+            return other is self
+
         try:
             return self._repr == other._repr
         except AttributeError:
diff --git a/qiskit/circuit/classicalregister.py b/qiskit/circuit/classicalregister.py
index 5efcd377e..8ceb59a9b 100644
--- a/qiskit/circuit/classicalregister.py
+++ b/qiskit/circuit/classicalregister.py
@@ -23,18 +23,18 @@ from .bit import Bit
 class Clbit(Bit):
     """Implement a classical bit."""
 
-    def __init__(self, register, index):
+    def __init__(self, register=None, index=None):
         """Creates a classical bit.
 
         Args:
-            register (ClassicalRegister): a classical register.
-            index (int): the index to insert the bit
+            register (ClassicalRegister): Optional. A classical register containing the bit.
+            index (int): Optional. The index of the bit in its containing register.
 
         Raises:
             CircuitError: if the provided register is not a valid :class:`ClassicalRegister`
         """
 
-        if isinstance(register, ClassicalRegister):
+        if register is None or isinstance(register, ClassicalRegister):
             super().__init__(register, index)
         else:
             raise CircuitError('Clbit needs a ClassicalRegister and %s was provided' %
diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py
index fb2d4b65c..f8be59ef6 100644
--- a/qiskit/circuit/quantumcircuit.py
+++ b/qiskit/circuit/quantumcircuit.py
@@ -33,7 +33,7 @@ from qiskit.qasm.exceptions import QasmError
 from qiskit.circuit.exceptions import CircuitError
 from qiskit.utils.deprecation import deprecate_function
 from .parameterexpression import ParameterExpression
-from .quantumregister import QuantumRegister, Qubit, AncillaRegister
+from .quantumregister import QuantumRegister, Qubit, AncillaRegister, AncillaQubit
 from .classicalregister import ClassicalRegister, Clbit
 from .parametertable import ParameterTable
 from .parametervector import ParameterVector
@@ -59,8 +59,8 @@ class QuantumCircuit:
     A circuit is a list of instructions bound to some registers.
 
     Args:
-        regs (list(:class:`Register`) or list(``int``)): The registers to be
-            included in the circuit.
+        regs (list(:class:`Register`) or list(``int``) or list(list(:class:`Bit`))): The
+            registers to be included in the circuit.
 
             * If a list of :class:`Register` objects, represents the :class:`QuantumRegister`
               and/or :class:`ClassicalRegister` objects to include in the circuit.
@@ -80,6 +80,9 @@ class QuantumCircuit:
                 * ``QuantumCircuit(4) # A QuantumCircuit with 4 qubits``
                 * ``QuantumCircuit(4, 3) # A QuantumCircuit with 4 qubits and 3 classical bits``
 
+            * If a list of python lists containing :class:`Bit` objects, a collection of
+              :class:`Bit` s to be added to the circuit.
+
 
         name (str): the name of the quantum circuit. If not set, an
             automatically generated string will be assigned.
@@ -146,7 +149,7 @@ class QuantumCircuit:
     extension_lib = "include \"qelib1.inc\";"
 
     def __init__(self, *regs, name=None, global_phase=0, metadata=None):
-        if any([not isinstance(reg, (QuantumRegister, ClassicalRegister)) for reg in regs]):
+        if any([not isinstance(reg, (list, QuantumRegister, ClassicalRegister)) for reg in regs]):
             # check if inputs are integers, but also allow e.g. 2.0
 
             try:
@@ -1012,7 +1015,10 @@ class QuantumCircuit:
                                    " with %s." % (regs,))
 
         for register in regs:
-            if register.name in [reg.name for reg in self.qregs + self.cregs]:
+            if (
+                    isinstance(register, Register)
+                    and any(register.name == reg.name for reg in self.qregs + self.cregs)
+            ):
                 raise CircuitError("register name \"%s\" already exists"
                                    % register.name)
 
@@ -1025,9 +1031,29 @@ class QuantumCircuit:
             elif isinstance(register, ClassicalRegister):
                 self.cregs.append(register)
                 self._clbits.extend(register)
+            elif isinstance(register, list):
+                self.add_bits(register)
             else:
                 raise CircuitError("expected a register")
 
+    def add_bits(self, bits):
+        """Add Bits to the circuit."""
+        duplicate_bits = set(self.qubits + self.clbits).intersection(bits)
+        if duplicate_bits:
+            raise CircuitError("Attempted to add bits found already in circuit: "
+                               "{}".format(duplicate_bits))
+
+        for bit in bits:
+            if isinstance(bit, AncillaQubit):
+                self._ancillas.append(bit)
+            elif isinstance(bit, Qubit):
+                self._qubits.append(bit)
+            elif isinstance(bit, Clbit):
+                self._clbits.append(bit)
+            else:
+                raise CircuitError("Expected an instance of Qubit, Clbit, or "
+                                   "AncillaQubit, but was passed {}".format(bit))
+
     def _check_dups(self, qubits):
         """Raise exception if list of qubits contains duplicates."""
         squbits = set(qubits)
diff --git a/qiskit/circuit/quantumregister.py b/qiskit/circuit/quantumregister.py
index c20ce56dc..21c829181 100644
--- a/qiskit/circuit/quantumregister.py
+++ b/qiskit/circuit/quantumregister.py
@@ -23,18 +23,18 @@ from .bit import Bit
 class Qubit(Bit):
     """Implement a quantum bit."""
 
-    def __init__(self, register, index):
+    def __init__(self, register=None, index=None):
         """Creates a qubit.
 
         Args:
-            register (QuantumRegister): a quantum register.
-            index (int): the index to insert the qubit
+            register (QuantumRegister): Optional. A quantum register containing the bit.
+            index (int): Optional. The index of the bit in its containing register.
 
         Raises:
             CircuitError: if the provided register is not a valid :class:`QuantumRegister`
         """
 
-        if isinstance(register, QuantumRegister):
+        if register is None or isinstance(register, QuantumRegister):
             super().__init__(register, index)
         else:
             raise CircuitError('Qubit needs a QuantumRegister and %s was provided' %
diff --git a/qiskit/circuit/register.py b/qiskit/circuit/register.py
index 033e6476f..4384e0dc3 100644
--- a/qiskit/circuit/register.py
+++ b/qiskit/circuit/register.py
@@ -37,11 +37,41 @@ class Register:
     prefix = 'reg'
     bit_type = None
 
-    def __init__(self, size, name=None):
+    def __init__(self, size=None, name=None, bits=None):
         """Create a new generic register.
+
+        Either the ``size`` or the ``bits`` argument must be provided. If
+        ``size`` is not None, the register will be pre-populated with bits of the
+        correct type.
+
+        Args:
+            size (int): Optional. The number of bits to include in the register.
+            name (str): Optional. The name of the register. If not provided, a
+               unique name will be auto-generated from the register type.
+            bits (list[Bit]): Optional. A list of Bit() instances to be used to
+               populate the register.
+
+        Raises:
+            CircuitError: if both the ``size`` and ``bits`` arguments are
+                provided, or if neither are.
+            CircuitError: if ``size`` is not valid.
+            CircuitError: if ``name`` is not a valid name according to the
+                OpenQASM spec.
+            CircuitError: if ``bits`` contained bits of an incorrect type.
         """
 
+        if (
+                (size, bits) == (None, None)
+                or (size is not None and bits is not None)
+        ):
+            raise CircuitError("Exactly one of the size or bits arguments can be "
+                               "provided. Provided size=%s bits=%s."
+                               % (size, bits))
+
         # validate (or cast) size
+        if bits is not None:
+            size = len(bits)
+
         try:
             valid_size = size == int(size)
         except (ValueError, TypeError):
@@ -74,7 +104,13 @@ class Register:
 
         self._hash = hash((type(self), self._name, self._size))
         self._repr = "%s(%d, '%s')" % (self.__class__.__qualname__, self.size, self.name)
-        self._bits = [self.bit_type(self, idx) for idx in range(size)]
+        if bits is not None:
+            if any(not isinstance(bit, self.bit_type) for bit in bits):
+                raise CircuitError("Provided bits did not all match "
+                                   "register type. bits=%s" % bits)
+            self._bits = list(bits)
+        else:
+            self._bits = [self.bit_type(self, idx) for idx in range(size)]
 
     def _update_bits_hash(self):
         for bit in self._bits:
@@ -149,7 +185,10 @@ class Register:
 
     def __eq__(self, other):
         """Two Registers are the same if they are of the same type
-        (i.e. quantum/classical), and have the same name and size.
+        (i.e. quantum/classical), and have the same name and size. Additionally,
+        if either Register contains new-style bits, the bits in both registers
+        will be checked for pairwise equality. If two registers are equal,
+        they will have behave identically when specified as circuit args.
 
         Args:
             other (Register): other Register
@@ -157,10 +196,22 @@ class Register:
         Returns:
             bool: `self` and `other` are equal.
         """
-        try:
-            return self._repr == other._repr
-        except AttributeError:
-            return False
+        if self is other:
+            return True
+
+        res = False
+        if type(self) is type(other) and \
+                self._repr == other._repr and \
+                all(
+                        # For new-style bits, check bitwise equality.
+                        sbit == obit
+                        for sbit, obit in zip(self, other)
+                        if None in
+                        (sbit._register, sbit._index,
+                         obit._register, obit._index)
+                ):
+            res = True
+        return res
 
     def __hash__(self):
         """Make object hashable, based on the name and size to hash."""
diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py
index e4000ba7b..94a3db303 100644
--- a/qiskit/dagcircuit/dagcircuit.py
+++ b/qiskit/dagcircuit/dagcircuit.py
@@ -30,8 +30,9 @@ import numpy as np
 import retworkx as rx
 
 from qiskit.circuit.quantumregister import QuantumRegister, Qubit
-from qiskit.circuit.classicalregister import ClassicalRegister
+from qiskit.circuit.classicalregister import ClassicalRegister, Clbit
 from qiskit.circuit.gate import Gate
+from qiskit.circuit.exceptions import CircuitError
 from qiskit.circuit.parameterexpression import ParameterExpression
 from qiskit.dagcircuit.exceptions import DAGCircuitError
 from qiskit.dagcircuit.dagnode import DAGNode
@@ -233,6 +234,32 @@ class DAGCircuit:
         for n in self.named_nodes(opname):
             self.remove_op_node(n)
 
+    def add_qubits(self, qubits):
+        """Add individual qubit wires."""
+        if any(not isinstance(qubit, Qubit) for qubit in qubits):
+            raise DAGCircuitError("not a Qubit instance.")
+
+        duplicate_qubits = set(self.qubits).intersection(qubits)
+        if duplicate_qubits:
+            raise DAGCircuitError("duplicate qubits %s" % duplicate_qubits)
+
+        self.qubits.extend(qubits)
+        for qubit in qubits:
+            self._add_wire(qubit)
+
+    def add_clbits(self, clbits):
+        """Add individual clbit wires."""
+        if any(not isinstance(clbit, Clbit) for clbit in clbits):
+            raise DAGCircuitError("not a Clbit instance.")
+
+        duplicate_clbits = set(self.clbits).intersection(clbits)
+        if duplicate_clbits:
+            raise DAGCircuitError("duplicate clbits %s" % duplicate_clbits)
+
+        self.clbits.extend(clbits)
+        for clbit in clbits:
+            self._add_wire(clbit)
+
     def add_qreg(self, qreg):
         """Add all wires in a quantum register."""
         if not isinstance(qreg, QuantumRegister):
@@ -268,7 +295,10 @@ class DAGCircuit:
         if wire not in self._wires:
             self._wires.add(wire)
 
-            wire_name = "%s[%s]" % (wire.register.name, wire.index)
+            try:
+                wire_name = "%s[%s]" % (wire.register.name, wire.index)
+            except CircuitError:
+                wire_name = "%s" % wire
 
             inp_node = DAGNode(type='in', name=wire_name, wire=wire)
             outp_node = DAGNode(type='out', name=wire_name, wire=wire)
diff --git a/releasenotes/notes/bit-classes-register-index-optional-9aa6feff4fce3524.yaml b/releasenotes/notes/bit-classes-register-index-optional-9aa6feff4fce3524.yaml
new file mode 100644
index 000000000..0f1c19a8c
--- /dev/null
+++ b/releasenotes/notes/bit-classes-register-index-optional-9aa6feff4fce3524.yaml
@@ -0,0 +1,7 @@
+---
+features:
+  - |
+    The constructors of the :class:`~qiskit.circuit.Bit` class and subclasses,
+    :class:`~qiskit.circuit.Qubit`, :class:`~qiskit.circuit.Clbit`, and
+    :class:`~qiskit.circuit.AncillaQubit`, have been updated such that their
+    two parameters, ``register`` and ``index`` are now optional.


Test patch:
diff --git a/test/python/circuit/test_bit.py b/test/python/circuit/test_bit.py
index f1b8a8156..58f45f0ff 100644
--- a/test/python/circuit/test_bit.py
+++ b/test/python/circuit/test_bit.py
@@ -90,3 +90,48 @@ class TestBitClass(QiskitTestCase):
         orig_reg.size = 3
         test_bit = bit.Bit(orig_reg, 0)
         self.assertNotEqual(test_bit, 3.14)
+
+    def test_old_style_bit_equality(self):
+        test_reg = mock.MagicMock(size=3, name='foo')
+        test_reg.__str__.return_value = "Register(3, 'foo')"
+
+        self.assertEqual(bit.Bit(test_reg, 0), bit.Bit(test_reg, 0))
+        self.assertNotEqual(bit.Bit(test_reg, 0), bit.Bit(test_reg, 2))
+
+        reg_copy = mock.MagicMock(size=3, name='foo')
+        reg_copy.__str__.return_value = "Register(3, 'foo')"
+
+        self.assertEqual(bit.Bit(test_reg, 0), bit.Bit(reg_copy, 0))
+        self.assertNotEqual(bit.Bit(test_reg, 0), bit.Bit(reg_copy, 1))
+
+        reg_larger = mock.MagicMock(size=4, name='foo')
+        reg_larger.__str__.return_value = "Register(4, 'foo')"
+
+        self.assertNotEqual(bit.Bit(test_reg, 0), bit.Bit(reg_larger, 0))
+
+        reg_renamed = mock.MagicMock(size=3, name='bar')
+        reg_renamed.__str__.return_value = "Register(3, 'bar')"
+
+        self.assertNotEqual(bit.Bit(test_reg, 0), bit.Bit(reg_renamed, 0))
+
+        reg_difftype = mock.MagicMock(size=3, name='bar')
+        reg_difftype.__str__.return_value = "QuantumRegister(3, 'bar')"
+
+        self.assertNotEqual(bit.Bit(test_reg, 0), bit.Bit(reg_difftype, 0))
+
+
+class TestNewStyleBit(QiskitTestCase):
+    """Test behavior of new-style bits."""
+
+    def test_bits_do_not_require_registers(self):
+        """Verify we can create a bit outside the context of a register."""
+        self.assertIsInstance(bit.Bit(), bit.Bit)
+
+    def test_newstyle_bit_equality(self):
+        """Verify bits instances are equal only to themselves."""
+        bit1 = bit.Bit()
+        bit2 = bit.Bit()
+
+        self.assertEqual(bit1, bit1)
+        self.assertNotEqual(bit1, bit2)
+        self.assertNotEqual(bit1, 3.14)
diff --git a/test/python/circuit/test_register.py b/test/python/circuit/test_register.py
new file mode 100644
index 000000000..b45b6d8ee
--- /dev/null
+++ b/test/python/circuit/test_register.py
@@ -0,0 +1,112 @@
+# This code is part of Qiskit.
+#
+# (C) Copyright IBM 2020.
+#
+# 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.
+
+# pylint: disable=missing-function-docstring
+
+"""Test library of quantum circuits."""
+
+from ddt import data, ddt
+
+from qiskit.test import QiskitTestCase
+from qiskit.circuit import bit
+from qiskit.circuit import QuantumRegister
+from qiskit.circuit import AncillaRegister
+from qiskit.circuit import ClassicalRegister
+
+from qiskit.circuit.exceptions import CircuitError
+
+
+@ddt
+class TestRegisterClass(QiskitTestCase):
+    """Tests for Register class."""
+
+    @data(QuantumRegister, ClassicalRegister, AncillaRegister)
+    def test_raise_on_init_with_invalid_size(self, reg_type):
+        with self.assertRaisesRegex(CircuitError, "must be an integer"):
+            _ = reg_type(1j, 'foo')
+
+    @data(QuantumRegister, ClassicalRegister, AncillaRegister)
+    def test_raise_if_init_passed_both_size_and_bits(self, reg_type):
+        bits = [reg_type.bit_type()]
+        with self.assertRaisesRegex(CircuitError, "Exactly one of the size or bits"):
+            _ = reg_type(1, 'foo', bits)
+
+    @data(QuantumRegister, ClassicalRegister, AncillaRegister)
+    def test_init_raise_if_bits_of_incorrect_type(self, reg_type):
+        bits = [bit.Bit()]
+        with self.assertRaisesRegex(CircuitError, "did not all match register type"):
+            _ = reg_type(bits=bits)
+
+    @data(QuantumRegister, ClassicalRegister, AncillaRegister)
+    def test_init_raise_if_passed_invalid_name(self, reg_type):
+        with self.assertRaisesRegex(CircuitError, "invalid OPENQASM register name"):
+            _ = reg_type(size=1, name='_q')
+
+    @data(QuantumRegister, ClassicalRegister, AncillaRegister)
+    def test_implicit_bit_construction_from_size(self, reg_type):
+        reg = reg_type(2)
+        self.assertEqual(len(reg), 2)
+        self.assertEqual(reg.size, 2)
+        self.assertTrue(all(isinstance(bit, reg.bit_type)
+                            for bit in reg))
+
+    @data(QuantumRegister, ClassicalRegister, AncillaRegister)
+    def test_implicit_size_calculation_from_bits(self, reg_type):
+        bits = [reg_type.bit_type() for _ in range(3)]
+        reg = reg_type(bits=bits)
+        self.assertEqual(reg.size, 3)
+
+    @data(QuantumRegister, ClassicalRegister, AncillaRegister)
+    def test_oldstyle_register_eq(self, reg_type):
+        test_reg = reg_type(3, 'foo')
+        self.assertEqual(test_reg, test_reg)
+
+        reg_copy = reg_type(3, 'foo')
+        self.assertEqual(reg_copy, test_reg)
+
+        reg_larger = reg_type(4, 'foo')
+        self.assertNotEqual(reg_larger, test_reg)
+
+        reg_renamed = reg_type(3, 'bar')
+        self.assertNotEqual(reg_renamed, test_reg)
+
+        difftype = ({QuantumRegister, ClassicalRegister, AncillaRegister} - {reg_type}).pop()
+        reg_difftype = difftype(3, 'foo')
+        self.assertNotEqual(reg_difftype, test_reg)
+
+    @data(QuantumRegister, ClassicalRegister, AncillaRegister)
+    def test_newstyle_register_eq(self, reg_type):
+        test_bits = [reg_type.bit_type() for _ in range(3)]
+        test_reg = reg_type(name='foo', bits=test_bits)
+        self.assertEqual(test_reg, test_reg)
+
+        reg_samebits = reg_type(name='foo', bits=test_bits)
+        self.assertEqual(reg_samebits, test_reg)
+
+        test_diffbits = [reg_type.bit_type() for _ in range(3)]
+        reg_diffbits = reg_type(name='foo', bits=test_diffbits)
+        self.assertNotEqual(reg_diffbits, test_reg)
+
+        reg_oldstyle = reg_type(3, 'foo')
+        self.assertNotEqual(reg_oldstyle, test_reg)
+
+        test_largerbits = [reg_type.bit_type() for _ in range(4)]
+        reg_larger = reg_type(name='foo', bits=test_largerbits)
+        self.assertNotEqual(reg_larger, test_reg)
+
+        reg_renamed = reg_type(name='bar', bits=test_bits)
+        self.assertNotEqual(reg_renamed, test_reg)
+
+        difftype = ({QuantumRegister, ClassicalRegister, AncillaRegister} - {reg_type}).pop()
+        bits_difftype = [difftype.bit_type() for _ in range(3)]
+        reg_difftype = difftype(name='foo', bits=bits_difftype)
+        self.assertNotEqual(reg_difftype, test_reg)
diff --git a/test/python/circuit/test_registerless_circuit.py b/test/python/circuit/test_registerless_circuit.py
index e5a577bf3..77ad19f1b 100644
--- a/test/python/circuit/test_registerless_circuit.py
+++ b/test/python/circuit/test_registerless_circuit.py
@@ -15,6 +15,7 @@
 import numpy
 
 from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
+from qiskit.circuit import Qubit, Clbit, AncillaQubit
 from qiskit.circuit.exceptions import CircuitError
 from qiskit.test import QiskitTestCase
 
@@ -43,6 +44,61 @@ class TestRegisterlessCircuit(QiskitTestCase):
         self.assertRaises(CircuitError, QuantumCircuit, 1, ClassicalRegister(2))
 
 
+class TestAddingBitsWithoutRegisters(QiskitTestCase):
+    """Test adding Bit instances outside of Registers."""
+
+    def test_circuit_constructor_on_bits(self):
+        """Verify we can add bits directly to a circuit."""
+        qubits = [Qubit(), Qubit()]
+        clbits = [Clbit()]
+        ancillas = [AncillaQubit(), AncillaQubit()]
+
+        qc = QuantumCircuit(qubits, clbits, ancillas)
+
+        self.assertEqual(qc.qubits, qubits)
+        self.assertEqual(qc.clbits, clbits)
+        self.assertEqual(qc.ancillas, ancillas)
+
+        self.assertEqual(qc.qregs, [])
+        self.assertEqual(qc.cregs, [])
+
+    def test_circuit_constructor_on_invalid_bits(self):
+        """Verify we raise if passed not a Bit."""
+        with self.assertRaisesRegex(CircuitError, "Expected an instance of"):
+            _ = QuantumCircuit([3.14])
+
+    def test_raise_if_bits_already_present(self):
+        """Verify we raise when attempting to add a Bit already in the circuit.
+        """
+        qubits = [Qubit()]
+
+        with self.assertRaisesRegex(CircuitError, "bits found already"):
+            _ = QuantumCircuit(qubits, qubits)
+
+        qc = QuantumCircuit(qubits)
+
+        with self.assertRaisesRegex(CircuitError, "bits found already"):
+            qc.add_bits(qubits)
+
+        qr = QuantumRegister(1, 'qr')
+        qc = QuantumCircuit(qr)
+
+        with self.assertRaisesRegex(CircuitError, "bits found already"):
+            qc.add_bits(qr[:])
+
+    def test_addding_individual_bit(self):
+        """Verify we can add a single bit to a circuit."""
+        qr = QuantumRegister(3, 'qr')
+        qc = QuantumCircuit(qr)
+
+        new_bit = Qubit()
+
+        qc.add_bits([new_bit])
+
+        self.assertEqual(qc.qubits, list(qr) + [new_bit])
+        self.assertEqual(qc.qregs, [qr])
+
+
 class TestGatesOnWires(QiskitTestCase):
     """Test gates on wires."""
 
diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py
index f280b0d81..b87c1af61 100644
--- a/test/python/dagcircuit/test_dagcircuit.py
+++ b/test/python/dagcircuit/test_dagcircuit.py
@@ -21,7 +21,7 @@ import retworkx as rx
 from qiskit.dagcircuit import DAGCircuit
 from qiskit.circuit import QuantumRegister
 from qiskit.circuit import ClassicalRegister, Clbit
-from qiskit.circuit import QuantumCircuit
+from qiskit.circuit import QuantumCircuit, Qubit
 from qiskit.circuit import Measure
 from qiskit.circuit import Reset
 from qiskit.circuit import Delay
@@ -176,6 +176,68 @@ class TestDagRegisters(QiskitTestCase):
         cr = ClassicalRegister(2)
         self.assertRaises(DAGCircuitError, dag.add_qreg, cr)
 
+    def test_add_qubits_invalid_qubits(self):
+        """Verify we raise if pass not a Qubit."""
+        dag = DAGCircuit()
+
+        with self.assertRaisesRegex(DAGCircuitError, "not a Qubit instance"):
+            dag.add_qubits([Clbit()])
+
+        with self.assertRaisesRegex(DAGCircuitError, "not a Qubit instance"):
+            dag.add_qubits([Qubit(), Clbit(), Qubit()])
+
+    def test_add_qubits_invalid_clbits(self):
+        """Verify we raise if pass not a Clbit."""
+        dag = DAGCircuit()
+
+        with self.assertRaisesRegex(DAGCircuitError, "not a Clbit instance"):
+            dag.add_clbits([Qubit()])
+
+        with self.assertRaisesRegex(DAGCircuitError, "not a Clbit instance"):
+            dag.add_clbits([Clbit(), Qubit(), Clbit()])
+
+    def test_raise_if_bits_already_present(self):
+        """Verify we raise when attempting to add a Bit already in the DAG."""
+        dag = DAGCircuit()
+        qubits = [Qubit(), Qubit()]
+        clbits = [Clbit(), Clbit()]
+
+        dag.add_qubits(qubits)
+        dag.add_clbits(clbits)
+
+        with self.assertRaisesRegex(DAGCircuitError, "duplicate qubits"):
+            dag.add_qubits(qubits)
+
+        with self.assertRaisesRegex(DAGCircuitError, "duplicate clbits "):
+            dag.add_clbits(clbits)
+
+    def test_raise_if_bits_already_present_from_register(self):
+        """Verify we raise when attempting to add a Bit already in the DAG."""
+        dag = DAGCircuit()
+        qr = QuantumRegister(2, 'q')
+        cr = ClassicalRegister(2, 'c')
+        dag.add_creg(cr)
+        dag.add_qreg(qr)
+
+        with self.assertRaisesRegex(DAGCircuitError, "duplicate qubits"):
+            dag.add_qubits(qr[:])
+
+        with self.assertRaisesRegex(DAGCircuitError, "duplicate clbits "):
+            dag.add_clbits(cr[:])
+
+    def test_addding_individual_bit(self):
+        """Verify we can add a individual bits to a DAG."""
+        qr = QuantumRegister(3, 'qr')
+        dag = DAGCircuit()
+        dag.add_qreg(qr)
+
+        new_bit = Qubit()
+
+        dag.add_qubits([new_bit])
+
+        self.assertEqual(dag.qubits, list(qr) + [new_bit])
+        self.assertEqual(list(dag.qregs.values()), [qr])
+
 
 class TestDagApplyOperation(QiskitTestCase):
     """Test adding an op node to a dag."""