Repository: Qiskit/qiskit

Issue:
Changing register size can corrupt circuit structures.
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues -->

### Information

- **Qiskit Terra version**: 0.15.0.dev0+63449c4
- **Python version**: Python 3.7.7
- **Operating system**: MacOs 10.15.3

### What is the current behavior?
If a register size is changed using the register "size" attribute and the size is smaller than the original, the system is left in an inconsistent state as there may be instructions using one of the bits past the bit index.  If the size is larger, a new bit is not added so errors occur if one attempts to use the "new" bit.  As well, any system or application code using registers or bits as keys to a map becomes inconsistent because the hash changes.


### Steps to reproduce the problem
reg = QuantumRegister(2)
reg.size = 3
qc  = QuantumCircuit(reg)
qc.h(2)      # fails because the bit was not added: list index out of range

reg = QuantumRegister(3)
qc = QuantumCircuit(reg)
qc.h(2)
reg.size=2
print(qc)     # fails because qubit is not available: qubit 2 not found

### What is the expected behavior?
Register size should be read-only.


### Suggested solutions

Register size should be made read-only because changing it changes the hash values for the register and its bits, prohibiting their use as keys for structures such as sets and maps, which would be invalidated as soon as the size changes.  Probably the 'name' attribute should also be made read-only as changing that also changes the hash and potentially invalidates keyed structures.




Discussion:
Hey! Can I give this issue a try?
@Arijit02 thanks for your interest. There are a few open questions to address here first.

@Cryoris Does aqua use resizable registers? The current implementation definitely has problems, but at least shrinking registers would appear to have worked in some cases.
As long as the register is not used in any structure that relies on the hash the current code works fine.  But if you're building a complex circuit where you want to put registers into a map (dict), for example, changing size or name invalidates that structure.  Shrinking registers will still cause this.

I would suggest that either registers need to read-only, or we document that they cannot safely be put into hash-dependent structures.  My preference would be to make them read-only as the other choice seems a little silly. 

It's not clear, but it seems doubtful that there is a useful hash not dependent on the register size and name because if you're using a map (dict), you likely want "equal" registers to hash the the same place, and equality is defined as a function of name and number of bits.  So the current hash seems to be the right one.
BTW this holds for Q/Cbits as well because their hashes are a function of their containing register's hashes.

Resolving patch:
diff --git a/qiskit/circuit/bit.py b/qiskit/circuit/bit.py
--- a/qiskit/circuit/bit.py
+++ b/qiskit/circuit/bit.py
@@ -46,12 +46,9 @@ def __init__(self, register=None, index=None):
 
             self._register = register
             self._index = index
-            self._update_hash()
-
-    def _update_hash(self):
-        self._hash = hash((self._register, self._index))
-        self._repr = "%s(%s, %s)" % (self.__class__.__name__,
-                                     self._register, self._index)
+            self._hash = hash((self._register, self._index))
+            self._repr = "%s(%s, %s)" % (self.__class__.__name__,
+                                         self._register, self._index)
 
     @property
     def register(self):
@@ -61,15 +58,6 @@ def register(self):
 
         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."""
@@ -78,15 +66,6 @@ def index(self):
 
         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):
diff --git a/qiskit/circuit/register.py b/qiskit/circuit/register.py
--- a/qiskit/circuit/register.py
+++ b/qiskit/circuit/register.py
@@ -112,39 +112,16 @@ def __init__(self, size=None, name=None, bits=None):
         else:
             self._bits = [self.bit_type(self, idx) for idx in range(size)]
 
-    def _update_bits_hash(self):
-        for bit in self._bits:
-            bit._update_hash()
-
     @property
     def name(self):
         """Get the register name."""
         return self._name
 
-    @name.setter
-    def name(self, value):
-        """Set the register name."""
-        if self.name_format.match(value) is None:
-            raise CircuitError("%s is an invalid OPENQASM register name. See appendix"
-                               " A of https://arxiv.org/pdf/1707.03429v2.pdf." % value)
-        self._name = value
-        self._hash = hash((type(self), self._name, self._size))
-        self._repr = "%s(%d, '%s')" % (self.__class__.__qualname__, self.size, self.name)
-        self._update_bits_hash()
-
     @property
     def size(self):
         """Get the register size."""
         return self._size
 
-    @size.setter
-    def size(self, value):
-        """Set the register size."""
-        self._size = value
-        self._hash = hash((type(self), self._name, self._size))
-        self._repr = "%s(%d, '%s')" % (self.__class__.__qualname__, self.size, self.name)
-        self._update_bits_hash()
-
     def __repr__(self):
         """Return the official string representing the register."""
         return self._repr


Test patch:
diff --git a/test/python/circuit/test_bit.py b/test/python/circuit/test_bit.py
--- a/test/python/circuit/test_bit.py
+++ b/test/python/circuit/test_bit.py
@@ -18,73 +18,11 @@
 
 from qiskit.test import QiskitTestCase
 from qiskit.circuit import bit
-from qiskit.circuit import quantumregister
-from qiskit.circuit import classicalregister
 
 
 class TestBitClass(QiskitTestCase):
     """Test library of boolean logic quantum circuits."""
 
-    def test_bit_hash_update_reg(self):
-        orig_reg = mock.MagicMock()
-        orig_reg.size = 3
-        new_reg = mock.MagicMock()
-        orig_reg.size = 4
-        test_bit = bit.Bit(orig_reg, 0)
-        orig_hash = hash(test_bit)
-        test_bit.register = new_reg
-        new_hash = hash(test_bit)
-        self.assertNotEqual(orig_hash, new_hash)
-
-    def test_bit_hash_update_index(self):
-        orig_reg = mock.MagicMock()
-        orig_reg.size = 4
-        test_bit = bit.Bit(orig_reg, 0)
-        orig_hash = hash(test_bit)
-        test_bit.index = 2
-        new_hash = hash(test_bit)
-        self.assertNotEqual(orig_hash, new_hash)
-
-    def test_qubit_hash_update_reg(self):
-        orig_reg = mock.MagicMock(spec=quantumregister.QuantumRegister)
-        orig_reg.size = 3
-        new_reg = mock.MagicMock(spec=quantumregister.QuantumRegister)
-        new_reg.size = 6
-        test_bit = quantumregister.Qubit(orig_reg, 0)
-        orig_hash = hash(test_bit)
-        test_bit.register = new_reg
-        new_hash = hash(test_bit)
-        self.assertNotEqual(orig_hash, new_hash)
-
-    def test_qubit_hash_update_index(self):
-        orig_reg = mock.MagicMock(spec=quantumregister.QuantumRegister)
-        orig_reg.size = 67
-        test_bit = quantumregister.Qubit(orig_reg, 0)
-        orig_hash = hash(test_bit)
-        test_bit.index = 2
-        new_hash = hash(test_bit)
-        self.assertNotEqual(orig_hash, new_hash)
-
-    def test_clbit_hash_update_reg(self):
-        orig_reg = mock.MagicMock(spec=classicalregister.ClassicalRegister)
-        orig_reg.size = 5
-        new_reg = mock.MagicMock(spec=classicalregister.ClassicalRegister)
-        new_reg.size = 53
-        test_bit = classicalregister.Clbit(orig_reg, 0)
-        orig_hash = hash(test_bit)
-        test_bit.register = new_reg
-        new_hash = hash(test_bit)
-        self.assertNotEqual(orig_hash, new_hash)
-
-    def test_clbit_hash_update_index(self):
-        orig_reg = mock.MagicMock(spec=classicalregister.ClassicalRegister)
-        orig_reg.size = 42
-        test_bit = classicalregister.Clbit(orig_reg, 0)
-        orig_hash = hash(test_bit)
-        test_bit.index = 2
-        new_hash = hash(test_bit)
-        self.assertNotEqual(orig_hash, new_hash)
-
     def test_bit_eq_invalid_type_comparison(self):
         orig_reg = mock.MagicMock()
         orig_reg.size = 3
diff --git a/test/python/circuit/test_circuit_registers.py b/test/python/circuit/test_circuit_registers.py
--- a/test/python/circuit/test_circuit_registers.py
+++ b/test/python/circuit/test_circuit_registers.py
@@ -47,14 +47,6 @@ def test_cregs(self):
         self.assertEqual(cr1.size, 10)
         self.assertEqual(type(cr1), ClassicalRegister)
 
-    def test_qreg_name_set_invalid(self):
-        """Test attempt to set an invalid name
-        """
-        qr1 = QuantumRegister(1)
-        # As per OPENQASM requirement, name cannot start with '_'
-        with self.assertRaises(CircuitError):
-            qr1.name = '_q'
-
     def test_aregs(self):
         """Test getting ancilla registers from circuit.
         """
@@ -508,43 +500,3 @@ def test_4_args_unitary_zip_expansion(self):
         for (gate, qargs, _) in circ.data:
             self.assertEqual(gate.name, 'unitary')
             self.assertEqual(len(qargs), 4)
-
-    def test_quantumregister_hash_upate_name(self):
-        """Test QuantumRegister hash changes on name update."""
-        test_reg = QuantumRegister(2)
-        orig_hash = hash(test_reg)
-        orig_bit_hashes = [hash(x) for x in test_reg]
-        test_reg.name = 'test_quantum'
-        new_hash = hash(test_reg)
-        new_bit_hashes = [hash(x) for x in test_reg]
-        self.assertNotEqual(orig_hash, new_hash)
-        for x in range(2):
-            self.assertNotEqual(orig_bit_hashes[x], new_bit_hashes[x])
-
-    def test_quantumregister_hash_upate_size(self):
-        """Test QuantumRegister hash changes on size update."""
-        test_reg = QuantumRegister(2)
-        orig_hash = hash(test_reg)
-        test_reg.size = 3
-        new_hash = hash(test_reg)
-        self.assertNotEqual(orig_hash, new_hash)
-
-    def test_classicalregister_hash_upate_name(self):
-        """Test ClassicalRegister hash changes on name update."""
-        test_reg = ClassicalRegister(2)
-        orig_hash = hash(test_reg)
-        orig_bit_hashes = [hash(x) for x in test_reg]
-        test_reg.name = 'test_classical'
-        new_hash = hash(test_reg)
-        new_bit_hashes = [hash(x) for x in test_reg]
-        self.assertNotEqual(orig_hash, new_hash)
-        for x in range(2):
-            self.assertNotEqual(orig_bit_hashes[x], new_bit_hashes[x])
-
-    def test_classicalregister_hash_upate_size(self):
-        """Test ClassicalRegister hash changes on size update."""
-        test_reg = ClassicalRegister(2)
-        orig_hash = hash(test_reg)
-        test_reg.size = 3
-        new_hash = hash(test_reg)
-        self.assertNotEqual(orig_hash, new_hash)