Repository: Qiskit/qiskit

Issue:
circuit.library.graph_state needs testing
The object `GraphState` (in `qiskit/circuit/library/graph_state.py`) needs testing.  [No line is currently coveraged](https://coveralls.io/builds/30439931/source?filename=qiskit/circuit/library/graph_state.py). 


Discussion:
Hi, I'd be interested in contributing if this issue is still available. This would be my first issue so I'm not completely sure where to start!
Hi, I would like to work on this issue, would it be possible to assign this to me if it is not being worked on?
Hi!

Great that you want to contribute 🎉 

So the task would be adding tests for the `GraphState` circuit. This concept is introduced in [this paper](https://arxiv.org/pdf/quant-ph/0307130.pdf) which you can look up for some context. 
A test should make sure that the implementation in Qiskit works (i.e. it runs w/o errors) and is correct (i.e. the output is correct). 

Ideally for such a circuit we would check the implementation on a theoretical level. This means we're checking the Qiskit implementation against what the maths says. This can e.g. be done by computing the unitary the circuit implements and then simulating the circuit in Qiskit. 
This is done e.g. for the `HiddenLinearFunction`:
https://github.com/Qiskit/qiskit-terra/blob/8771e595a67e584f7ce1bac2d70db84516cd3432/test/python/circuit/test_library.py#L150
and the `QFT`:
https://github.com/Qiskit/qiskit-terra/blob/8771e595a67e584f7ce1bac2d70db84516cd3432/test/python/circuit/test_library.py#L346
To do this you could have a look at Eq. (9) and following in [the paper](https://arxiv.org/pdf/quant-ph/0307130.pdf), where it describes what state the circuit builds (called `|G>` in the paper). For this check we would compute the matrix that prepares `|G>` as described in the paper and compare it to the `GraphState`.

Another simpler way, which would already be a good start, is to compare the circuit to  a hardcoded reference circuit. This is e.g. done for the `IQP` circuit:
https://github.com/Qiskit/qiskit-terra/blob/8771e595a67e584f7ce1bac2d70db84516cd3432/test/python/circuit/test_library.py#L190

You can check this reference files and let me know if you have any questions! What's the tactic you would like to pursue?
Hi @Cryoris!

Thank you so much for going into such details and explaining this so clearly! Your description has provided more than enough background for me to work on this 🙂

I would like to make a check on a theoretical level, it seems quite straightforward to calculate the matrix by hand for a particular graph. Would it be fine if I pick a smaller graph that can be implemented by up to 4 qubits to be used for testing, e.g., the graph No. 3 in FIG. 4 of [the paper](https://arxiv.org/pdf/quant-ph/0307130.pdf)? I am also considering creating multiple tests for graphs of various sizes, but let me know if that would be too much.

Thank you again for guidance!
Hi again!

I just wanted to give a quick update about this issue. I have looked into testing the graph states a bit more and I have come up with a piece of code that creates the expected unitary for a given adjacency matrix. I thought this might be a relevant update, because my previous point from the above comment that I would be calculating the matrix by hand is not valid anymore. I think it makes more sense to numerically calculate the expected matrix based on the adjacency matrix, since that would allow to change the test by simply changing the adjacency matrix. I can see that this is the approach used in other test cases, e.g., for `HiddenLinearFunction`. This was most likely the approach that was expected to begin with, so I am sorry to have figured that out just now 🙂

As a quick outline, the way I calculate the expected matrix from the adjacency matrix is quite simple. First, I create the matrix for Hadamard gates for the number of qubits extracted from the adjacency matrix. Secondly, I create the matrices for the CZ gates based on the adjacency matrix. This step is slightly more involved, since I need to get the CZ gate for any given number of qubits and control-target connections. I do that by looping through an identity matrix and setting the value of the diagonal elements to -1 where both the control and target are in state |1>. Lastly, all the matrices are multiplied together to get the expected unitary. I have tested this approach and the results look good.

I would now like to work on adding the mentioned code to `test/python/circuit/test_library.py`. Once the code is properly implemented and I am satisfied with the addition, I would like to create a pull request, if that would be fine 🙂
Hi @mantcep,

sounds great that you found a way to compute the generic unitary transformation that creates the graph state. A fixed comparison would also be good, but if we can do it generically that's of course better. 

It is easier to comment on the code if we can see it, so feel free to open a pull request already now and name it `[WIP] Your title` to show that it is still work in progress. Instead of looping through all indices you might also be able to compute the indices of control and target qubit and directly check their states. But I'll give more feedback once you opened the PR!

Sounds like a good plan 👍 



Resolving patch:
diff --git a/qiskit/circuit/library/graph_state.py b/qiskit/circuit/library/graph_state.py
--- a/qiskit/circuit/library/graph_state.py
+++ b/qiskit/circuit/library/graph_state.py
@@ -20,6 +20,7 @@
 
 import numpy as np
 from qiskit import QuantumCircuit
+from qiskit.circuit.exceptions import CircuitError
 
 
 class GraphState(QuantumCircuit):
@@ -69,9 +70,17 @@ def __init__(self,
         Args:
             adjacency_matrix: input graph as n-by-n list of 0-1 lists
 
+        Raises:
+            CircuitError: If adjacency_matrix is not symmetric.
+
         The circuit prepares a graph state with the given adjacency
         matrix.
         """
+
+        adjacency_matrix = np.asarray(adjacency_matrix)
+        if not np.allclose(adjacency_matrix, adjacency_matrix.transpose()):
+            raise CircuitError("The adjacency matrix must be symmetric.")
+
         num_qubits = len(adjacency_matrix)
         super().__init__(num_qubits, name="graph: %s" % (adjacency_matrix))
 


Test patch:
diff --git a/test/python/circuit/test_library.py b/test/python/circuit/test_library.py
--- a/test/python/circuit/test_library.py
+++ b/test/python/circuit/test_library.py
@@ -31,13 +31,13 @@
                                     WeightedAdder, Diagonal, NLocal, TwoLocal, RealAmplitudes,
                                     EfficientSU2, ExcitationPreserving, PauliFeatureMap,
                                     ZFeatureMap, ZZFeatureMap, MCMT, MCMTVChain, GMS,
-                                    HiddenLinearFunction)
+                                    HiddenLinearFunction, GraphState)
 from qiskit.circuit.random.utils import random_circuit
 from qiskit.converters.circuit_to_dag import circuit_to_dag
 from qiskit.exceptions import QiskitError
 from qiskit.circuit.library import (XGate, RXGate, RYGate, RZGate, CRXGate, CCXGate, SwapGate,
                                     RXXGate, RYYGate, HGate, ZGate, CXGate, CZGate, CHGate)
-from qiskit.quantum_info import Statevector, Operator
+from qiskit.quantum_info import Statevector, Operator, Clifford
 from qiskit.quantum_info.random import random_unitary
 from qiskit.quantum_info.states import state_fidelity
 
@@ -185,6 +185,51 @@ def test_non_symmetric_raises(self):
             HiddenLinearFunction([[1, 1, 0], [1, 0, 1], [1, 1, 1]])
 
 
+@ddt
+class TestGraphStateLibrary(QiskitTestCase):
+    """Test the graph state circuit."""
+
+    def assertGraphStateIsCorrect(self, adjacency_matrix, graph_state):
+        """Check the stabilizers of the graph state against the expected stabilizers.
+        Based on https://arxiv.org/pdf/quant-ph/0307130.pdf, Eq. (6).
+        """
+
+        stabilizers = Clifford(graph_state).stabilizer.pauli.to_labels()
+
+        expected_stabilizers = []  # keep track of all expected stabilizers
+        num_vertices = len(adjacency_matrix)
+        for vertex_a in range(num_vertices):
+            stabilizer = [None] * num_vertices  # Paulis must be put into right place
+            for vertex_b in range(num_vertices):
+                if vertex_a == vertex_b:  # self-connection --> 'X'
+                    stabilizer[vertex_a] = 'X'
+                elif adjacency_matrix[vertex_a][vertex_b] != 0:  # vertices connected --> 'Z'
+                    stabilizer[vertex_b] = 'Z'
+                else:  # else --> 'I'
+                    stabilizer[vertex_b] = 'I'
+
+            # need to reverse for Qiskit's tensoring order
+            expected_stabilizers.append(''.join(stabilizer)[::-1])
+
+        self.assertListEqual(expected_stabilizers, stabilizers)
+
+    @data(
+        [[0, 1, 0, 0, 1], [1, 0, 1, 0, 0], [0, 1, 0, 1, 0], [0, 0, 1, 0, 1], [1, 0, 0, 1, 0]]
+    )
+    def test_graph_state(self, adjacency_matrix):
+        """Verify the GraphState by checking if the circuit has the expected stabilizers."""
+        graph_state = GraphState(adjacency_matrix)
+        self.assertGraphStateIsCorrect(adjacency_matrix, graph_state)
+
+    @data(
+        [[1, 1, 0], [1, 0, 1], [1, 1, 1]]
+    )
+    def test_non_symmetric_raises(self, adjacency_matrix):
+        """Test that adjacency matrix is required to be symmetric."""
+        with self.assertRaises(CircuitError):
+            GraphState(adjacency_matrix)
+
+
 class TestIQPLibrary(QiskitTestCase):
     """Test library of IQP quantum circuits."""