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 commutator_diagonal_coulomb_operator.py"""
13
14import unittest
15import warnings
16
17from openfermion.ops.operators import FermionOperator
18from openfermion.transforms.opconversions import get_fermion_operator
19from openfermion.hamiltonians import jellium_model
20from openfermion.utils import commutator, Grid
21from openfermion.transforms.opconversions import normal_ordered
22from openfermion.transforms.opconversions\
23    .commutator_diagonal_coulomb_operator import (
24    commutator_ordered_diagonal_coulomb_with_two_body_operator)
25from openfermion.testing.testing_utils import (
26    random_diagonal_coulomb_hamiltonian)
27
28
29class DiagonalHamiltonianCommutatorTest(unittest.TestCase):
30
31    def test_commutator(self):
32        operator_a = (
33            FermionOperator('0^ 0', 0.3) + FermionOperator('1^ 1', 0.1j) +
34            FermionOperator('1^ 0^ 1 0', -0.2) + FermionOperator('1^ 3') +
35            FermionOperator('3^ 0') + FermionOperator('3^ 2', 0.017) -
36            FermionOperator('2^ 3', 1.99) + FermionOperator('3^ 1^ 3 1', .09) +
37            FermionOperator('2^ 0^ 2 0', .126j) + FermionOperator('4^ 2^ 4 2') +
38            FermionOperator('3^ 0^ 3 0'))
39
40        operator_b = (
41            FermionOperator('3^ 1', 0.7) + FermionOperator('1^ 3', -9.) +
42            FermionOperator('1^ 0^ 3 0', 0.1) -
43            FermionOperator('3^ 0^ 1 0', 0.11) + FermionOperator('3^ 2^ 3 2') +
44            FermionOperator('3^ 1^ 3 1', -1.37) + FermionOperator('4^ 2^ 4 2') +
45            FermionOperator('4^ 1^ 4 1') + FermionOperator('1^ 0^ 4 0', 16.7) +
46            FermionOperator('1^ 0^ 4 3', 1.67) +
47            FermionOperator('4^ 3^ 5 2', 1.789j) +
48            FermionOperator('6^ 5^ 4 1', -11.789j))
49
50        reference = normal_ordered(commutator(operator_a, operator_b))
51        result = commutator_ordered_diagonal_coulomb_with_two_body_operator(
52            operator_a, operator_b)
53
54        diff = result - reference
55        self.assertTrue(diff.isclose(FermionOperator.zero()))
56
57    def test_nonstandard_second_arg(self):
58        operator_a = (FermionOperator('0^ 0', 0.3) +
59                      FermionOperator('1^ 1', 0.1j) +
60                      FermionOperator('2^ 0^ 2 0', -0.2) +
61                      FermionOperator('2^ 1^ 2 1', -0.2j) +
62                      FermionOperator('1^ 3') + FermionOperator('3^ 0') +
63                      FermionOperator('4^ 4', -1.4j))
64
65        operator_b = (FermionOperator('4^ 1^ 3 0', 0.1) -
66                      FermionOperator('3^ 0^ 1 0', 0.11))
67
68        reference = (FermionOperator('1^ 0^ 1 0', -0.11) +
69                     FermionOperator('3^ 0^ 1 0', 0.011j) +
70                     FermionOperator('3^ 0^ 3 0', 0.11) +
71                     FermionOperator('3^ 2^ 0^ 2 1 0', -0.022j) +
72                     FermionOperator('4^ 1^ 3 0', -0.03 - 0.13j) +
73                     FermionOperator('4^ 2^ 1^ 3 2 0', -0.02 + 0.02j))
74
75        res = commutator_ordered_diagonal_coulomb_with_two_body_operator(
76            operator_a, operator_b)
77
78        self.assertTrue(res.isclose(reference))
79
80    def test_add_to_existing_result(self):
81        prior_terms = FermionOperator('0^ 1')
82        operator_a = FermionOperator('2^ 1')
83        operator_b = FermionOperator('0^ 2')
84
85        commutator_ordered_diagonal_coulomb_with_two_body_operator(
86            operator_a, operator_b, prior_terms=prior_terms)
87
88        self.assertTrue(prior_terms.isclose(FermionOperator.zero()))
89
90    def test_integration_jellium_hamiltonian_with_negation(self):
91        hamiltonian = normal_ordered(
92            jellium_model(Grid(2, 3, 1.), plane_wave=False))
93
94        part_a = FermionOperator.zero()
95        part_b = FermionOperator.zero()
96
97        add_to_a_or_b = 0  # add to a if 0; add to b if 1
98        for term, coeff in hamiltonian.terms.items():
99            # Partition terms in the Hamiltonian into part_a or part_b
100            if add_to_a_or_b:
101                part_a += FermionOperator(term, coeff)
102            else:
103                part_b += FermionOperator(term, coeff)
104            add_to_a_or_b ^= 1
105
106        reference = normal_ordered(commutator(part_a, part_b))
107        result = commutator_ordered_diagonal_coulomb_with_two_body_operator(
108            part_a, part_b)
109
110        self.assertTrue(result.isclose(reference))
111
112        negative = commutator_ordered_diagonal_coulomb_with_two_body_operator(
113            part_b, part_a)
114        result += negative
115
116        self.assertTrue(result.isclose(FermionOperator.zero()))
117
118    def test_no_warning_on_nonstandard_input_second_arg(self):
119        with warnings.catch_warnings(record=True) as w:
120            operator_a = FermionOperator('3^ 2^ 3 2')
121            operator_b = FermionOperator('4^ 3^ 4 1')
122
123            reference = FermionOperator('4^ 3^ 2^ 4 2 1')
124            result = (
125                commutator_ordered_diagonal_coulomb_with_two_body_operator(
126                    operator_a, operator_b))
127
128            self.assertFalse(w)
129
130            # Result should still be correct even though we hit the warning.
131            self.assertTrue(result.isclose(reference))
132
133    def test_warning_on_bad_input_first_arg(self):
134        with warnings.catch_warnings(record=True) as w:
135            operator_a = FermionOperator('4^ 3^ 2 1')
136            operator_b = FermionOperator('3^ 2^ 3 2')
137
138            reference = normal_ordered(commutator(operator_a, operator_b))
139            result = (
140                commutator_ordered_diagonal_coulomb_with_two_body_operator(
141                    operator_a, operator_b))
142
143            self.assertTrue(len(w) == 1)
144            self.assertIn('Defaulted to standard commutator evaluation',
145                          str(w[-1].message))
146
147            # Result should still be correct in this case.
148            diff = result - reference
149            self.assertTrue(diff.isclose(FermionOperator.zero()))
150
151    def test_integration_random_diagonal_coulomb_hamiltonian(self):
152        hamiltonian1 = normal_ordered(
153            get_fermion_operator(
154                random_diagonal_coulomb_hamiltonian(n_qubits=7)))
155        hamiltonian2 = normal_ordered(
156            get_fermion_operator(
157                random_diagonal_coulomb_hamiltonian(n_qubits=7)))
158
159        reference = normal_ordered(commutator(hamiltonian1, hamiltonian2))
160        result = commutator_ordered_diagonal_coulomb_with_two_body_operator(
161            hamiltonian1, hamiltonian2)
162
163        self.assertTrue(result.isclose(reference))
164