1#   Licensed under the Apache License, Version 2.0 (the "License");
2#   you may not use this file except in compliance with the License.
3#   You may obtain a copy of the License at
4#
5#       http://www.apache.org/licenses/LICENSE-2.0
6#
7#   Unless required by applicable law or agreed to in writing, software
8#   distributed under the License is distributed on an "AS IS" BASIS,
9#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10#   See the License for the specific language governing permissions and
11#   limitations under the License.
12"""Tests for bravyi_kitaev_tree.py."""
13
14import unittest
15
16import numpy
17
18from openfermion.ops.operators import (FermionOperator, QubitOperator)
19from openfermion.transforms.opconversions import bravyi_kitaev_tree, \
20    jordan_wigner
21from openfermion.linalg import eigenspectrum
22from openfermion.hamiltonians import number_operator
23
24
25class BravyiKitaevTransformTest(unittest.TestCase):
26
27    def test_bravyi_kitaev_tree_transform(self):
28        # Check that the QubitOperators are two-term.
29        lowering = bravyi_kitaev_tree(FermionOperator(((3, 0),)))
30        raising = bravyi_kitaev_tree(FermionOperator(((3, 1),)))
31        self.assertEqual(len(raising.terms), 2)
32        self.assertEqual(len(lowering.terms), 2)
33
34        #  Test the locality invariant for N=2^d qubits
35        # (c_j majorana is always log2N+1 local on qubits)
36        n_qubits = 16
37        invariant = numpy.log2(n_qubits) + 1
38        for index in range(n_qubits):
39            operator = bravyi_kitaev_tree(FermionOperator(((index, 0),)),
40                                          n_qubits)
41            qubit_terms = operator.terms.items()  # Get the majorana terms.
42
43            for item in qubit_terms:
44                coeff = item[1]
45
46                #  Identify the c majorana terms by real
47                #  coefficients and check their length.
48                if not isinstance(coeff, complex):
49                    self.assertEqual(len(item[0]), invariant)
50
51        #  Hardcoded coefficient test on 16 qubits
52        lowering = bravyi_kitaev_tree(FermionOperator(((9, 0),)), n_qubits)
53        raising = bravyi_kitaev_tree(FermionOperator(((9, 1),)), n_qubits)
54
55        correct_operators_c = ((7, 'Z'), (8, 'Z'), (9, 'X'), (11, 'X'), (15,
56                                                                         'X'))
57        correct_operators_d = ((7, 'Z'), (9, 'Y'), (11, 'X'), (15, 'X'))
58
59        self.assertEqual(lowering.terms[correct_operators_c], 0.5)
60        self.assertEqual(lowering.terms[correct_operators_d], 0.5j)
61        self.assertEqual(raising.terms[correct_operators_d], -0.5j)
62        self.assertEqual(raising.terms[correct_operators_c], 0.5)
63
64    def test_bk_identity(self):
65        self.assertTrue(
66            bravyi_kitaev_tree(FermionOperator(())) == QubitOperator(()))
67
68    def test_bk_n_qubits_too_small(self):
69        with self.assertRaises(ValueError):
70            bravyi_kitaev_tree(FermionOperator('2^ 3^ 5 0'), n_qubits=4)
71
72    def test_bk_jw_number_operator(self):
73        # Check if number operator has the same spectrum in both
74        # BK and JW representations
75        n = number_operator(1, 0)
76        jw_n = jordan_wigner(n)
77        bk_n = bravyi_kitaev_tree(n)
78
79        # Diagonalize and make sure the spectra are the same.
80        jw_spectrum = eigenspectrum(jw_n)
81        bk_spectrum = eigenspectrum(bk_n)
82
83        self.assertAlmostEqual(
84            0., numpy.amax(numpy.absolute(jw_spectrum - bk_spectrum)))
85
86    def test_bk_jw_number_operators(self):
87        # Check if a number operator has the same spectrum in both
88        # JW and BK representations
89        n_qubits = 2
90        n1 = number_operator(n_qubits, 0)
91        n2 = number_operator(n_qubits, 1)
92        n = n1 + n2
93
94        jw_n = jordan_wigner(n)
95        bk_n = bravyi_kitaev_tree(n)
96
97        # Diagonalize and make sure the spectra are the same.
98        jw_spectrum = eigenspectrum(jw_n)
99        bk_spectrum = eigenspectrum(bk_n)
100
101        self.assertAlmostEqual(
102            0., numpy.amax(numpy.absolute(jw_spectrum - bk_spectrum)))
103
104    def test_bk_jw_number_operator_scaled(self):
105        # Check if number operator has the same spectrum in both
106        # JW and BK representations
107        n_qubits = 1
108        n = number_operator(n_qubits, 0, coefficient=2)  # eigenspectrum (0,2)
109        jw_n = jordan_wigner(n)
110        bk_n = bravyi_kitaev_tree(n)
111
112        # Diagonalize and make sure the spectra are the same.
113        jw_spectrum = eigenspectrum(jw_n)
114        bk_spectrum = eigenspectrum(bk_n)
115
116        self.assertAlmostEqual(
117            0., numpy.amax(numpy.absolute(jw_spectrum - bk_spectrum)))
118
119    def test_bk_jw_hopping_operator(self):
120        # Check if the spectrum fits for a single hoppping operator
121        ho = FermionOperator(((1, 1), (4, 0))) + FermionOperator(
122            ((4, 1), (1, 0)))
123        jw_ho = jordan_wigner(ho)
124        bk_ho = bravyi_kitaev_tree(ho)
125
126        # Diagonalize and make sure the spectra are the same.
127        jw_spectrum = eigenspectrum(jw_ho)
128        bk_spectrum = eigenspectrum(bk_ho)
129
130        self.assertAlmostEqual(
131            0., numpy.amax(numpy.absolute(jw_spectrum - bk_spectrum)))
132
133    def test_bk_jw_majoranas(self):
134        # Check if the Majorana operators have the same spectrum
135        # irrespectively of the transform.
136
137        a = FermionOperator(((1, 0),))
138        a_dag = FermionOperator(((1, 1),))
139
140        c = a + a_dag
141        d = 1j * (a_dag - a)
142
143        c_spins = [jordan_wigner(c), bravyi_kitaev_tree(c)]
144        d_spins = [jordan_wigner(d), bravyi_kitaev_tree(d)]
145
146        c_spectrum = [eigenspectrum(c_spins[0]), eigenspectrum(c_spins[1])]
147        d_spectrum = [eigenspectrum(d_spins[0]), eigenspectrum(d_spins[1])]
148
149        self.assertAlmostEqual(
150            0., numpy.amax(numpy.absolute(c_spectrum[0] - c_spectrum[1])))
151        self.assertAlmostEqual(
152            0., numpy.amax(numpy.absolute(d_spectrum[0] - d_spectrum[1])))
153
154    def test_bk_jw_integration(self):
155        # This is a legacy test, which was a minimal failing example when
156        # optimization for hermitian operators was used.
157
158        # Minimal failing example:
159        fo = FermionOperator(((3, 1),))
160
161        jw = jordan_wigner(fo)
162        bk = bravyi_kitaev_tree(fo)
163
164        jw_spectrum = eigenspectrum(jw)
165        bk_spectrum = eigenspectrum(bk)
166
167        self.assertAlmostEqual(
168            0., numpy.amax(numpy.absolute(jw_spectrum - bk_spectrum)))
169
170    def test_bk_jw_integration_original(self):
171        # This is a legacy test, which was an example proposed by Ryan,
172        # failing when optimization for hermitian operators was used.
173        fermion_operator = FermionOperator(((3, 1), (2, 1), (1, 0), (0, 0)),
174                                           -4.3)
175        fermion_operator += FermionOperator(((3, 1), (1, 0)), 8.17)
176        fermion_operator += 3.2 * FermionOperator()
177
178        # Map to qubits and compare matrix versions.
179        jw_qubit_operator = jordan_wigner(fermion_operator)
180        bk_qubit_operator = bravyi_kitaev_tree(fermion_operator)
181
182        # Diagonalize and make sure the spectra are the same.
183        jw_spectrum = eigenspectrum(jw_qubit_operator)
184        bk_spectrum = eigenspectrum(bk_qubit_operator)
185        self.assertAlmostEqual(0.,
186                               numpy.amax(
187                                   numpy.absolute(jw_spectrum - bk_spectrum)),
188                               places=5)
189