Repository: QuantestPy/quantestpy (language: python)

Issue:
Implement the iteration of ancilla_is_zero method for different initial states
- [ ] Iteration for all the calculation basis (only when # of qubits is small?)
- [ ] Iteration for randomly chosen states

Note that ancilla qubit is always |0> (or |1>?). Only the system qubits are subject to change in iteration.


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml
index 3dbb370..d54ed53 100644
--- a/.github/workflows/github-actions.yml
+++ b/.github/workflows/github-actions.yml
@@ -32,4 +32,6 @@ jobs:
           python3 -m unittest test.test_test_circuit
           python3 -m unittest test.test_circuit_assert_equal_to_operator
           python3 -m unittest test.test_circuit_assert_is_zero
+          python3 -m unittest test.test_circuit_assert_ancilla_is_zero
           python3 -m unittest test.test_converter
+


Test patch:
diff --git a/quantestpy/circuit.py b/quantestpy/circuit.py
index e5da2b0..7267e54 100644
--- a/quantestpy/circuit.py
+++ b/quantestpy/circuit.py
@@ -1,6 +1,9 @@
 import unittest
 import numpy as np
 from typing import Union
+import itertools
+import traceback
+import re
 from qiskit import QuantumCircuit
 
 from quantestpy import operator
@@ -125,3 +128,86 @@ def _assert_is_zero_for_one_qubit(qubit: int) -> bool:
             + "entangled with other qubits."
         msg = ut_test_case._formatMessage(msg, error_msg)
         raise QuantestPyAssertionError(msg)
+
+
+def assert_ancilla_is_zero(ancilla_qubits: list,
+                           qasm: str = None,
+                           test_circuit: TestCircuit = None,
+                           number_of_decimal_places: int = 5,
+                           msg=None) -> None:
+
+    if qasm is None and test_circuit is None:
+        raise QuantestPyError(
+            "Missing qasm or test circuit."
+        )
+
+    if qasm is not None and test_circuit is not None:
+        raise QuantestPyError(
+            "Qasm and test circuit must not both be given."
+        )
+
+    if qasm is not None:
+        raise QuantestPyError(
+            "Loading qasm is not yet implemented."
+        )
+
+    if not isinstance(ancilla_qubits, list):
+        raise QuantestPyError(
+            "ancilla_qubits must be a list of integer(s) as qubit's ID(s)."
+        )
+
+    num_qubit = test_circuit._num_qubit
+
+    # system qubits <=> ancilla qubits
+    system_qubits = [qubit for qubit in range(num_qubit)
+                     if qubit not in ancilla_qubits]
+
+    def _add_x_gate_in_front(gates: list, qubit: int) -> dict:
+        x_gate = {'name': 'x',
+                  'target_qubit': [qubit],
+                  'control_qubit': [],
+                  'control_value': []}
+        gates.insert(0, x_gate)
+
+    all_combinations_of_system_qubits = []
+    for size in range(len(system_qubits)+1):
+        c = list(itertools.combinations(system_qubits, size))
+        all_combinations_of_system_qubits += c
+
+    error_msgs_from_assert_is_zero = []
+    for comb_of_sys_qubits in all_combinations_of_system_qubits:
+
+        # add x gate(s) in test_circuit
+        for system_qubit in comb_of_sys_qubits:
+            _add_x_gate_in_front(test_circuit._gates, system_qubit)
+
+        # assertion using assert_is_zero
+        try:
+            assert_is_zero(test_circuit=test_circuit,
+                           qubits=ancilla_qubits,
+                           number_of_decimal_places=number_of_decimal_places)
+
+        except QuantestPyAssertionError as e:
+            t = traceback.format_exception_only(type(e), e)[0]
+            error_msgs_from_assert_is_zero.append(t)
+
+        # remove x gate(s) from test_circuit, i.e. reset test_circuit.
+        for _ in comb_of_sys_qubits:
+            del test_circuit._gates[0]
+
+    if len(error_msgs_from_assert_is_zero) == 0:
+        return None  # = assertion non-error
+
+    error_qubits = []
+    for err_m_from_assert_is_zero in list(set(error_msgs_from_assert_is_zero)):
+        error_qubits_str_list = re.findall(r'\d+', err_m_from_assert_is_zero)
+
+        for error_qubit_str in error_qubits_str_list:
+            error_qubits.append(int(error_qubit_str))
+
+    error_qubits = list(set(error_qubits))
+
+    error_msg = f"qubit(s) {error_qubits} are either non-zero or " \
+        + "entangled with other qubits."
+    msg = ut_test_case._formatMessage(msg, error_msg)
+    raise QuantestPyAssertionError(msg)
diff --git a/test/test_circuit_assert_ancilla_is_zero.py b/test/test_circuit_assert_ancilla_is_zero.py
new file mode 100644
index 0000000..bfccd68
--- /dev/null
+++ b/test/test_circuit_assert_ancilla_is_zero.py
@@ -0,0 +1,99 @@
+import unittest
+import traceback
+
+from quantestpy import TestCircuit
+from quantestpy import circuit
+from quantestpy.exceptions import QuantestPyAssertionError
+
+
+class TestCircuitAssertAncillaIsZero(unittest.TestCase):
+    """
+    How to execute this test:
+    $ pwd
+    {Your directory where you git-cloned quantestpy}/quantestpy
+    $ python -m unittest test.test_circuit_assert_ancilla_is_zero
+    ...
+    ----------------------------------------------------------------------
+    Ran 2 tests in 0.017s
+
+    OK
+    """
+
+    def test_regular_1(self,):
+        test_circuit = TestCircuit(4)
+        # V
+        test_circuit.add_gate(
+            {"name": "cx", "control_qubit": [0], "target_qubit": [1],
+             "control_value": [1]}
+        )
+        test_circuit.add_gate(
+            {"name": "cx", "control_qubit": [1], "target_qubit": [2],
+             "control_value": [1]}
+        )
+
+        # uncomputation
+        test_circuit.add_gate(
+            {"name": "cx", "control_qubit": [1], "target_qubit": [3],
+             "control_value": [1]}
+        )
+
+        # V^{-1}
+        test_circuit.add_gate(
+            {"name": "cx", "control_qubit": [1], "target_qubit": [2],
+             "control_value": [1]}
+        )
+        test_circuit.add_gate(
+            {"name": "cx", "control_qubit": [0], "target_qubit": [1],
+             "control_value": [1]}
+        )
+
+        self.assertIsNone(
+            circuit.assert_ancilla_is_zero(
+                test_circuit=test_circuit,
+                ancilla_qubits=[1, 2]
+            )
+        )
+
+    def test_irregular_1(self,):
+        test_circuit = TestCircuit(4)
+        # V
+        test_circuit.add_gate(
+            {"name": "cx", "control_qubit": [0], "target_qubit": [1],
+             "control_value": [1]}
+        )
+        test_circuit.add_gate(
+            {"name": "cx", "control_qubit": [1], "target_qubit": [2],
+             "control_value": [1]}
+        )
+
+        # uncomputation
+        test_circuit.add_gate(
+            {"name": "cx", "control_qubit": [1], "target_qubit": [3],
+             "control_value": [1]}
+        )
+
+        # V^{-1}: Wrong order!!
+        test_circuit.add_gate(
+            {"name": "cx", "control_qubit": [0], "target_qubit": [1],
+             "control_value": [1]}
+        )
+        test_circuit.add_gate(
+            {"name": "cx", "control_qubit": [1], "target_qubit": [2],
+             "control_value": [1]}
+        )
+
+        try:
+            circuit.assert_ancilla_is_zero(
+                test_circuit=test_circuit,
+                ancilla_qubits=[1, 2]
+            )
+
+        except QuantestPyAssertionError as e:
+
+            expected_error_msg = \
+                "quantestpy.exceptions.QuantestPyAssertionError: qubit(s) " \
+                + "[2] are either non-zero or entangled with other qubits.\n"
+
+            actual_error_msg = traceback.format_exception_only(type(e), e)[0]
+
+            self.assertEqual(expected_error_msg, actual_error_msg)