Repository: Qiskit/qiskit

Issue:
HoareOptimizer does not handle CZ correctly 
<!-- ⚠️ 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**: master
- **Python version**: 3.7.8
- **Operating system**: MacOS

### What is the current behavior?

Consider the following program, where `circuit_in` is optimized by `HoareOptimizer` pass as `circuit_out` :
```python
from numpy import pi
from qiskit import *
from qiskit.converters import *
from qiskit.transpiler.passes import HoareOptimizer
from qiskit.transpiler.passmanager import PassManager

circuit_in = QuantumCircuit(2)
circuit_in.h(0)
circuit_in.x(1)
circuit_in.cz(0, 1)
circuit_in.h(0)
circuit_in.measure_all()

def print_circuit_and_counts(circuit):
    print(circuit)
    backend = BasicAer.get_backend('qasm_simulator')    
    print(execute(circuit, backend=backend, optimization_level=0).result().get_counts())

print_circuit_and_counts(circuit_in)

pm = PassManager()
pm.append(HoareOptimizer(size=10))
circuit_out = pm.run(circuit_in)

print_circuit_and_counts(circuit_out)
```
```
        ┌───┐   ┌───┐ ░ ┌─┐   
   q_0: ┤ H ├─■─┤ H ├─░─┤M├───
        ├───┤ │ └───┘ ░ └╥┘┌─┐
   q_1: ┤ X ├─■───────░──╫─┤M├
        └───┘         ░  ║ └╥┘
meas: 2/═════════════════╩══╩═
                         0  1 
{'11': 1024}
              ░ ┌─┐   
   q_0: ──────░─┤M├───
        ┌───┐ ░ └╥┘┌─┐
   q_1: ┤ X ├─░──╫─┤M├
        └───┘ ░  ║ └╥┘
meas: 2/═════════╩══╩═
                 0  1 
{'10': 1024}
```

The optimization fully removes the `H-CZ-H` sequence wrongly, breaking the semantics equivalence of the transformation.

### What is the expected behavior?

I'm not fully sure if the original paper deals with phase (maybe the root of the problem). From what I remember, it seems to me that the qubit `q_1` falls into a "trivial condition" and the CZ should be replaced by a Z gate in `q_0`.

Notice that `circuit_in.cz(1, 0)` (instead of `circuit_in.cz(0, 1)` does not break the counting, since `HoareOptimizer` does not detect any optimization, which is also unexpected. Maybe the problem is related to #4980?  






Discussion:
Same issue is caused if you use `CRZ` or `CU1`.

What's happening is that `HoareOptimizer` thinks these gates act trivially and removes them. Rephrasing lines [127-133](https://github.com/Qiskit/qiskit-terra/blob/master/qiskit/transpiler/passes/optimization/hoare_opt.py#L127), one of the above gates is considered to act trivially if whenever the control qubits are in |1> state, the target qubit is in a definite state, meaning always in |0> or |1> but not in a superposition. But the problem is that
 
<img src="https://render.githubusercontent.com/render/math?math=CZ_{0,1} (a |0 > %2B b |1>)_0 \otimes 1>_1 = (a |0 > - b |1>)_0 \otimes 1>_1">

which is not actually a trivial action, unless only measurements follow, (neither i think it's considered trivial in the original paper, see [Def 5.3](https://arxiv.org/pdf/1810.00375.pdf)) and so CZ should not be removed.

I think the correct statement would be that these controlled gates act indeed trivially if:
* the control qubits are always in state |1> and the target qubit is in a definite state, or
* the control qubits are in a state orthogonal to |1> state.
(i am talking more generally about multiple-controlled versions of these gates.)
Specifically, it's currently missing a test which should assert that if the control qubits can be in |1> then it's the only state that they can be.

Furthermore, as you are suggesting an other optimization step could be to replace a Controlled-Unitary gate with the Unitary if the control qubits are always in |1> state. But as far as i understand, this wasn't the initial intention of `HoareOptimizer`.

If you think this seems right, i will try to implement it.
Hmm.. you are right that this case is not the paper's trivial case. So the original papers removes gates only but cannot replace gates? Probably @ajavadia has an opinion here.

Resolving patch:
diff --git a/qiskit/transpiler/passes/optimization/_gate_extension.py b/qiskit/transpiler/passes/optimization/_gate_extension.py
--- a/qiskit/transpiler/passes/optimization/_gate_extension.py
+++ b/qiskit/transpiler/passes/optimization/_gate_extension.py
@@ -12,11 +12,11 @@
 
 """
 Dynamically extend Gate classes with functions required for the Hoare
-optimizer, namely triviality- and post-conditionsto.
-A return value of 'true' for triviality conditions indicates the gate is
-always trivial, provided the qubit is in a classical state.
-Functions/gates that are omitted here are assumed to always be
-non-trivial and/or have unknown post-conditions.
+optimizer, namely triviality-conditions and post-conditions.
+If `_trivial_if` returns `True` and the qubit is in a classical state
+then the gate is trivial.
+If a gate has no `_trivial_if`, then is assumed to be non-trivial.
+If a gate has no `_postconditions`, then is assumed to have unknown post-conditions.
 """
 try:
     from z3 import Not, And
@@ -27,7 +27,7 @@
 from qiskit.circuit.library.standard_gates import IGate, XGate, YGate, ZGate
 from qiskit.circuit.library.standard_gates import CXGate, CCXGate, CYGate, CZGate
 from qiskit.circuit.library.standard_gates import TGate, TdgGate, SGate, SdgGate, RZGate, U1Gate
-from qiskit.circuit.library.standard_gates import SwapGate, CSwapGate, CRZGate, CU1Gate
+from qiskit.circuit.library.standard_gates import SwapGate, CSwapGate, CRZGate, CU1Gate, MCU1Gate
 
 if HAS_Z3:
     # FLIP GATES #
@@ -62,15 +62,18 @@
     TdgGate._trivial_if = lambda self, x1: True
     TdgGate._postconditions = lambda self, x1, y1: y1 == x1
 
-    # RzGate = U1Gate
+    # RzGate
     RZGate._trivial_if = lambda self, x1: True
     RZGate._postconditions = lambda self, x1, y1: y1 == x1
-    CRZGate._trivial_if = lambda self, x1: True
     CRZGate._postconditions = lambda self, x1, y1: y1 == x1
+
+    #  U1Gate
     U1Gate._trivial_if = lambda self, x1: True
     U1Gate._postconditions = lambda self, x1, y1: y1 == x1
     CU1Gate._trivial_if = lambda self, x1: True
     CU1Gate._postconditions = lambda self, x1, y1: y1 == x1
+    MCU1Gate._trivial_if = lambda self, x1: True
+    MCU1Gate._postconditions = lambda self, x1, y1: y1 == x1
 
     # MULTI-QUBIT GATES #
     # SwapGate
diff --git a/qiskit/transpiler/passes/optimization/hoare_opt.py b/qiskit/transpiler/passes/optimization/hoare_opt.py
--- a/qiskit/transpiler/passes/optimization/hoare_opt.py
+++ b/qiskit/transpiler/passes/optimization/hoare_opt.py
@@ -12,10 +12,13 @@
 
 """ Pass for Hoare logic circuit optimization. """
 from qiskit.transpiler.basepasses import TransformationPass
-from qiskit.circuit import ControlledGate, Gate
+from qiskit.circuit import QuantumRegister, ControlledGate, Gate
+from qiskit.dagcircuit import DAGCircuit
 from qiskit.extensions.unitary import UnitaryGate
 from qiskit.quantum_info.operators.predicates import matrix_equal
 from qiskit.transpiler.exceptions import TranspilerError
+from qiskit.circuit.exceptions import CircuitError
+from qiskit.circuit.library.standard_gates import CZGate, CU1Gate, MCU1Gate
 from . import _gate_extension  # pylint: disable=unused-import
 
 try:
@@ -126,7 +129,7 @@ def _test_gate(self, gate, ctrl_ones, trgtvar):
         else:
             if isinstance(triv_cond, bool):
                 if triv_cond and len(trgtvar) == 1:
-                    self.solver.add(And(ctrl_ones, Not(trgtvar[0])))
+                    self.solver.add(Not(And(ctrl_ones, trgtvar[0])))
                     sol1 = self.solver.check() == unsat
                     self.solver.pop()
                     self.solver.push()
@@ -140,6 +143,55 @@ def _test_gate(self, gate, ctrl_ones, trgtvar):
         self.solver.pop()
         return trivial
 
+    def _remove_control(self, gate, ctrlvar, trgtvar):
+        """use z3 sat solver to determine if all control qubits are in 1 state,
+             and if so replace the Controlled - U by U.
+        Args:
+            gate (Gate): gate to inspect
+            ctrlvar (list(BoolRef)): z3 variables corresponding to latest state
+                                     of control qubits
+            trgtvar (list(BoolRef)): z3 variables corresponding to latest state
+                                     of target qubits
+        Returns:
+            Tuple(bool, DAGCircuit, List)::
+              * bool:if controlled gate can be replaced.
+              * DAGCircuit: with U applied to the target qubits.
+              * List: with indices of target qubits.
+        """
+        remove = False
+
+        qarg = QuantumRegister(gate.num_qubits)
+        dag = DAGCircuit()
+        dag.add_qreg(qarg)
+
+        qb = list(range(len(ctrlvar), gate.num_qubits))  # last qubits as target.
+
+        if isinstance(gate, ControlledGate):
+            remove = self._check_removal(ctrlvar)
+
+        # try with other qubit as 'target'.
+        if isinstance(gate, (CZGate, CU1Gate, MCU1Gate)):
+            while not remove and qb[0] > 0:
+                qb[0] = qb[0] - 1
+                ctrl_vars = ctrlvar[: qb[0]] + ctrlvar[qb[0] + 1 :] + trgtvar
+                remove = self._check_removal(ctrl_vars)
+
+        if remove:
+            qubits = [qarg[qi] for qi in qb]
+            dag.apply_operation_back(gate.base_gate, qubits)
+
+        return remove, dag, qb
+
+    def _check_removal(self, ctrlvar):
+        ctrl_ones = And(*ctrlvar)
+
+        self.solver.push()
+        self.solver.add(Not(ctrl_ones))
+        remove = self.solver.check() == unsat
+        self.solver.pop()
+
+        return remove
+
     def _traverse_dag(self, dag):
         """traverse DAG in topological order
             for each gate check: if any control is 0, or
@@ -151,10 +203,22 @@ def _traverse_dag(self, dag):
         """
         for node in dag.topological_op_nodes():
             gate = node.op
-            _, ctrlvar, trgtqb, trgtvar = self._seperate_ctrl_trgt(node)
+            ctrlqb, ctrlvar, trgtqb, trgtvar = self._seperate_ctrl_trgt(node)
 
             ctrl_ones = And(*ctrlvar)
 
+            remove_ctrl, new_dag, qb_idx = self._remove_control(gate, ctrlvar, trgtvar)
+
+            if remove_ctrl:
+                dag.substitute_node_with_dag(node, new_dag)
+                gate = gate.base_gate
+                node.op = gate
+                node.name = gate.name
+                node.qargs = [(ctrlqb + trgtqb)[qi] for qi in qb_idx]
+                _, ctrlvar, trgtqb, trgtvar = self._seperate_ctrl_trgt(node)
+
+                ctrl_ones = And(*ctrlvar)
+
             trivial = self._test_gate(gate, ctrl_ones, trgtvar)
             if trivial:
                 dag.remove_op_node(node)
@@ -210,8 +274,11 @@ def _is_identity(self, sequence):
             bool: if gate sequence combines to identity
         """
         assert len(sequence) == 2
-
-        gate1, gate2 = sequence[0].op, sequence[1].op.inverse()
+        # some Instructions (e.g measurements) may not have an inverse.
+        try:
+            gate1, gate2 = sequence[0].op, sequence[1].op.inverse()
+        except CircuitError:
+            return False
         par1, par2 = gate1.params, gate2.params
         def1, def2 = gate1.definition, gate2.definition
 


Test patch:
diff --git a/test/python/transpiler/test_hoare_opt.py b/test/python/transpiler/test_hoare_opt.py
--- a/test/python/transpiler/test_hoare_opt.py
+++ b/test/python/transpiler/test_hoare_opt.py
@@ -293,6 +293,44 @@ def test_targetsuccessive_identity_advanced_removal(self):
 
         self.assertEqual(result, circuit_to_dag(expected))
 
+    def test_control_removal(self):
+        """Should replace CX by X and CZ by Z."""
+        circuit = QuantumCircuit(2)
+        circuit.x(0)
+        circuit.cx(0, 1)
+
+        expected = QuantumCircuit(2)
+        expected.x(0)
+        expected.x(1)
+
+        stv = Statevector.from_label("0" * circuit.num_qubits)
+        self.assertEqual(stv @ circuit, stv @ expected)
+
+        pass_ = HoareOptimizer(size=5)
+        result = pass_.run(circuit_to_dag(circuit))
+
+        self.assertEqual(result, circuit_to_dag(expected))
+
+        circuit = QuantumCircuit(2)
+        circuit.h(0)
+        circuit.x(1)
+        circuit.cz(0, 1)
+        circuit.h(0)
+
+        expected = QuantumCircuit(2)
+        expected.h(0)
+        expected.x(1)
+        expected.z(0)
+        expected.h(0)
+
+        stv = Statevector.from_label("0" * circuit.num_qubits)
+        self.assertEqual(stv @ circuit, stv @ expected)
+
+        pass_ = HoareOptimizer(size=5)
+        result = pass_.run(circuit_to_dag(circuit))
+
+        self.assertEqual(result, circuit_to_dag(expected))
+
     def test_is_identity(self):
         """The is_identity function determines whether a pair of gates
         forms the identity, when ignoring control qubits.