qiskit/test/python/synthesis/test_cnot_phase_synthesis.py

275 lines
14 KiB
Python

# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2019.
#
# 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.
"""Test cnot circuit and cnot-phase circuit synthesis algorithms"""
import unittest
import ddt
from qiskit.circuit import QuantumCircuit, QuantumRegister
from qiskit.circuit.library import UnitaryGate
from qiskit.quantum_info.operators import Operator
from qiskit.synthesis.linear import synth_cnot_count_full_pmh
from qiskit.synthesis.linear_phase import synth_cnot_phase_aam
from test import QiskitTestCase # pylint: disable=wrong-import-order
@ddt.ddt
class TestGraySynth(QiskitTestCase):
"""Test the Gray-Synth algorithm."""
def test_gray_synth(self):
"""Test synthesis of a small parity network via gray_synth.
The algorithm should take the following matrix as an input:
S =
[[0, 1, 1, 0, 1, 1],
[0, 1, 1, 0, 1, 0],
[0, 0, 0, 1, 1, 0],
[1, 0, 0, 1, 1, 1],
[0, 1, 0, 0, 1, 0],
[0, 1, 0, 0, 1, 0]]
Along with some rotation angles:
['s', 't', 'z', 's', 't', 't'])
which together specify the Fourier expansion in the sum-over-paths representation
of a quantum circuit.
And should return the following circuit (or an equivalent one):
┌───┐┌───┐┌───┐┌───┐┌───┐┌───┐┌───┐┌───┐┌───┐┌───┐┌───┐┌───┐┌───┐┌───┐
q_0: |0>──────────┤ X ├┤ X ├┤ T ├┤ X ├┤ X ├┤ X ├┤ X ├┤ T ├┤ X ├┤ T ├┤ X ├┤ X ├┤ Z ├┤ X ├
└─┬─┘└─┬─┘└───┘└─┬─┘└─┬─┘└─┬─┘└─┬─┘└───┘└─┬─┘└───┘└─┬─┘└─┬─┘└───┘└─┬─┘
q_1: |0>────────────┼────┼─────────■────┼────┼────┼─────────┼─────────┼────┼─────────■──
│ │ │ │ │ │ │ │
q_2: |0>───────■────■────┼──────────────■────┼────┼─────────┼────■────┼────┼────────────
┌───┐┌─┴─┐┌───┐ │ │ │ │ ┌─┴─┐ │ │
q_3: |0>┤ S ├┤ X ├┤ S ├──■───────────────────┼────┼─────────■──┤ X ├──┼────┼────────────
└───┘└───┘└───┘ │ │ └───┘ │ │
q_4: |0>─────────────────────────────────────■────┼───────────────────■────┼────────────
│ │
q_5: |0>──────────────────────────────────────────■────────────────────────■────────────
"""
cnots = [
[0, 1, 1, 0, 1, 1],
[0, 1, 1, 0, 1, 0],
[0, 0, 0, 1, 1, 0],
[1, 0, 0, 1, 1, 1],
[0, 1, 0, 0, 1, 0],
[0, 1, 0, 0, 1, 0],
]
angles = ["s", "t", "z", "s", "t", "t"]
c_gray = synth_cnot_phase_aam(cnots, angles)
unitary_gray = UnitaryGate(Operator(c_gray))
# Create the circuit displayed above:
q = QuantumRegister(6, "q")
c_compare = QuantumCircuit(q)
c_compare.s(q[3])
c_compare.cx(q[2], q[3])
c_compare.s(q[3])
c_compare.cx(q[2], q[0])
c_compare.cx(q[3], q[0])
c_compare.t(q[0])
c_compare.cx(q[1], q[0])
c_compare.cx(q[2], q[0])
c_compare.cx(q[4], q[0])
c_compare.cx(q[5], q[0])
c_compare.t(q[0])
c_compare.cx(q[3], q[0])
c_compare.t(q[0])
c_compare.cx(q[2], q[3])
c_compare.cx(q[4], q[0])
c_compare.cx(q[5], q[0])
c_compare.z(q[0])
c_compare.cx(q[1], q[0])
unitary_compare = UnitaryGate(Operator(c_compare))
# Check if the two circuits are equivalent
self.assertEqual(unitary_gray, unitary_compare)
def test_paper_example(self):
"""Test synthesis of a diagonal operator from the paper.
The diagonal operator in Example 4.2
U|x> = e^(2.pi.i.f(x))|x>,
where
f(x) = 1/8*(x1^x2 + x0 + x0^x3 + x0^x1^x2 + x0^x1^x3 + x0^x1)
The algorithm should take the following matrix as an input:
S = [[0, 1, 1, 1, 1, 1],
[1, 0, 0, 1, 1, 1],
[1, 0, 0, 1, 0, 0],
[0, 0, 1, 0, 1, 0]]
and only T gates as phase rotations,
And should return the following circuit (or an equivalent one):
┌───┐┌───┐ ┌───┐┌───┐┌───┐┌───┐┌───┐┌───┐┌───┐┌───┐ ┌───┐┌───┐
q_0: |0>┤ T ├┤ X ├─────┤ T ├┤ X ├┤ X ├┤ T ├┤ X ├┤ T ├┤ X ├┤ T ├─────┤ X ├┤ X ├
├───┤└─┬─┘┌───┐└───┘└─┬─┘└─┬─┘└───┘└─┬─┘└───┘└─┬─┘└───┘┌───┐└─┬─┘└─┬─┘
q_1: |0>┤ X ├──┼──┤ T ├───────■────┼─────────┼─────────┼───────┤ X ├──■────┼──
└─┬─┘ │ └───┘ │ │ │ └─┬─┘ │
q_2: |0>──■────┼───────────────────┼─────────■─────────┼─────────■─────────┼──
│ │ │ │
q_3: |0>───────■───────────────────■───────────────────■───────────────────■──
"""
cnots = [[0, 1, 1, 1, 1, 1], [1, 0, 0, 1, 1, 1], [1, 0, 0, 1, 0, 0], [0, 0, 1, 0, 1, 0]]
angles = ["t"] * 6
c_gray = synth_cnot_phase_aam(cnots, angles)
unitary_gray = UnitaryGate(Operator(c_gray))
# Create the circuit displayed above:
q = QuantumRegister(4, "q")
c_compare = QuantumCircuit(q)
c_compare.t(q[0])
c_compare.cx(q[2], q[1])
c_compare.cx(q[3], q[0])
c_compare.t(q[0])
c_compare.t(q[1])
c_compare.cx(q[1], q[0])
c_compare.cx(q[3], q[0])
c_compare.t(q[0])
c_compare.cx(q[2], q[0])
c_compare.t(q[0])
c_compare.cx(q[3], q[0])
c_compare.t(q[0])
c_compare.cx(q[2], q[1])
c_compare.cx(q[1], q[0])
c_compare.cx(q[3], q[0])
unitary_compare = UnitaryGate(Operator(c_compare))
# Check if the two circuits are equivalent
self.assertEqual(unitary_gray, unitary_compare)
def test_ccz(self):
"""Test synthesis of the doubly-controlled Z gate.
The diagonal operator in Example 4.3
U|x> = e^(2.pi.i.f(x))|x>,
where
f(x) = 1/8*(x0 + x1 + x2 - x0^x1 - x0^x2 - x1^x2 + x0^x1^x2)
The algorithm should take the following matrix as an input:
S = [[1, 0, 0, 1, 1, 0, 1],
[0, 1, 0, 1, 0, 1, 1],
[0, 0, 1, 0, 1, 1, 1]]
and only T and T* gates as phase rotations,
And should return the following circuit (or an equivalent one):
┌───┐
q_0: |0>┤ T ├───────■──────────────■───────────────────■──────────────■──
└───┘┌───┐┌─┴─┐┌───┐ │ │ ┌─┴─┐
q_1: |0>─────┤ T ├┤ X ├┤ T*├───────┼─────────■─────────┼─────────■──┤ X ├
└───┘└───┘└───┘┌───┐┌─┴─┐┌───┐┌─┴─┐┌───┐┌─┴─┐┌───┐┌─┴─┐└───┘
q_2: |0>────────────────────┤ T ├┤ X ├┤ T*├┤ X ├┤ T*├┤ X ├┤ T ├┤ X ├─────
└───┘└───┘└───┘└───┘└───┘└───┘└───┘└───┘
"""
cnots = [[1, 0, 0, 1, 1, 0, 1], [0, 1, 0, 1, 0, 1, 1], [0, 0, 1, 0, 1, 1, 1]]
angles = ["t", "t", "t", "tdg", "tdg", "tdg", "t"]
c_gray = synth_cnot_phase_aam(cnots, angles)
unitary_gray = UnitaryGate(Operator(c_gray))
# Create the circuit displayed above:
q = QuantumRegister(3, "q")
c_compare = QuantumCircuit(q)
c_compare.t(q[0])
c_compare.t(q[1])
c_compare.cx(q[0], q[1])
c_compare.tdg(q[1])
c_compare.t(q[2])
c_compare.cx(q[0], q[2])
c_compare.tdg(q[2])
c_compare.cx(q[1], q[2])
c_compare.tdg(q[2])
c_compare.cx(q[0], q[2])
c_compare.t(q[2])
c_compare.cx(q[1], q[2])
c_compare.cx(q[0], q[1])
unitary_compare = UnitaryGate(Operator(c_compare))
# Check if the two circuits are equivalent
self.assertEqual(unitary_gray, unitary_compare)
@ddt.ddt
class TestPatelMarkovHayes(QiskitTestCase):
"""Test the Patel-Markov-Hayes algorithm for synthesizing linear
CNOT-only circuits."""
def test_patel_markov_hayes(self):
"""Test synthesis of a small linear circuit
(example from paper, Figure 3).
The algorithm should take the following matrix as an input:
S = [[1, 1, 0, 0, 0, 0],
[1, 0, 0, 1, 1, 0],
[0, 1, 0, 0, 1, 0],
[1, 1, 1, 1, 1, 1],
[1, 1, 0, 1, 1, 1],
[0, 0, 1, 1, 1, 0]]
And should return the following circuit (or an equivalent one):
┌───┐
q_0: |0>──────────┤ X ├──────────────────────────────────────────■────■────■──
└─┬─┘┌───┐ ┌─┴─┐ │ │
q_1: |0>────────────■──┤ X ├────────────────────────────────■──┤ X ├──┼────┼──
┌───┐ └─┬─┘┌───┐ ┌───┐ ┌─┴─┐└───┘ │ │
q_2: |0>─────┤ X ├───────┼──┤ X ├───────■──┤ X ├───────■──┤ X ├───────┼────┼──
┌───┐└─┬─┘ │ └─┬─┘┌───┐┌─┴─┐└─┬─┘ │ └───┘ │ ┌─┴─┐
q_3: |0>┤ X ├──┼─────────■────┼──┤ X ├┤ X ├──■────■────┼──────────────┼──┤ X ├
└─┬─┘ │ │ └─┬─┘├───┤ │ ┌─┴─┐ ┌─┴─┐└───┘
q_4: |0>──■────┼──────────────■────■──┤ X ├───────┼──┤ X ├──────────┤ X ├─────
│ └─┬─┘ ┌─┴─┐└───┘ └───┘
q_5: |0>───────■────────────────────────■───────┤ X ├─────────────────────────
└───┘
"""
state = [
[1, 1, 0, 0, 0, 0],
[1, 0, 0, 1, 1, 0],
[0, 1, 0, 0, 1, 0],
[1, 1, 1, 1, 1, 1],
[1, 1, 0, 1, 1, 1],
[0, 0, 1, 1, 1, 0],
]
c_patel = synth_cnot_count_full_pmh(state)
unitary_patel = UnitaryGate(Operator(c_patel))
# Create the circuit displayed above:
q = QuantumRegister(6, "q")
c_compare = QuantumCircuit(q)
c_compare.cx(q[4], q[3])
c_compare.cx(q[5], q[2])
c_compare.cx(q[1], q[0])
c_compare.cx(q[3], q[1])
c_compare.cx(q[4], q[2])
c_compare.cx(q[4], q[3])
c_compare.cx(q[5], q[4])
c_compare.cx(q[2], q[3])
c_compare.cx(q[3], q[2])
c_compare.cx(q[3], q[5])
c_compare.cx(q[2], q[4])
c_compare.cx(q[1], q[2])
c_compare.cx(q[0], q[1])
c_compare.cx(q[0], q[4])
c_compare.cx(q[0], q[3])
unitary_compare = UnitaryGate(Operator(c_compare))
# Check if the two circuits are equivalent
self.assertEqual(unitary_patel, unitary_compare)
if __name__ == "__main__":
unittest.main()