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"""Faster commutators for two-body operators with diagonal Coulomb terms."""
13
14import warnings
15
16from openfermion.ops.operators import FermionOperator
17from openfermion.transforms.opconversions import term_reordering
18
19
20def commutator_ordered_diagonal_coulomb_with_two_body_operator(
21        operator_a, operator_b, prior_terms=None):
22    """Compute the commutator of two-body operators provided that both are
23    normal-ordered and that the first only has diagonal Coulomb interactions.
24
25    Args:
26        operator_a: The first FermionOperator argument of the commutator.
27            All terms must be normal-ordered, and furthermore either hopping
28            operators (i^ j) or diagonal Coulomb operators (i^ i or i^ j^ i j).
29        operator_b: The second FermionOperator argument of the commutator.
30            operator_b can be any arbitrary two-body operator.
31        prior_terms (optional): The initial FermionOperator to add to.
32
33    Returns:
34        The commutator, or the commutator added to prior_terms if provided.
35
36    Notes:
37        The function could be readily extended to the case of arbitrary
38        two-body operator_a given that operator_b has the desired form;
39        however, the extra check slows it down without desirable added utility.
40    """
41    if prior_terms is None:
42        prior_terms = FermionOperator.zero()
43
44    for term_a in operator_a.terms:
45        coeff_a = operator_a.terms[term_a]
46        for term_b in operator_b.terms:
47            coeff_b = operator_b.terms[term_b]
48
49            coefficient = coeff_a * coeff_b
50
51            # If term_a == term_b the terms commute, nothing to add.
52            if term_a == term_b or not term_a or not term_b:
53                continue
54
55            # Case 1: both operators are two-body, operator_a is i^ j^ i j.
56            if (len(term_a) == len(term_b) == 4 and
57                    term_a[0][0] == term_a[2][0] and
58                    term_a[1][0] == term_a[3][0]):
59                _commutator_two_body_diagonal_with_two_body(
60                    term_a, term_b, coefficient, prior_terms)
61
62            # Case 2: commutator of a 1-body and a 2-body operator
63            elif (len(term_b) == 4 and
64                  len(term_a) == 2) or (len(term_a) == 4 and len(term_b) == 2):
65                _commutator_one_body_with_two_body(term_a, term_b, coefficient,
66                                                   prior_terms)
67
68            # Case 3: both terms are one-body operators (both length 2)
69            elif len(term_a) == 2 and len(term_b) == 2:
70                _commutator_one_body_with_one_body(term_a, term_b, coefficient,
71                                                   prior_terms)
72
73            # Final case (case 4): violation of the input promise. Still
74            # compute the commutator, but warn the user.
75            else:
76                warnings.warn('Defaulted to standard commutator evaluation '
77                              'due to an out-of-spec operator.')
78                additional = FermionOperator.zero()
79                additional.terms[term_a + term_b] = coefficient
80                additional.terms[term_b + term_a] = -coefficient
81                additional = term_reordering.normal_ordered(additional)
82
83                prior_terms += additional
84
85    return prior_terms
86
87
88def _commutator_one_body_with_one_body(one_body_action_a, one_body_action_b,
89                                       coefficient, prior_terms):
90    """Compute the commutator of two one-body operators specified by actions.
91
92    Args:
93        one_body_action_a, one_body_action_b (tuple): single terms of one-body
94            FermionOperators (i^ j or i^ i).
95        coefficient (complex float): coefficient of the commutator.
96        prior_terms (FermionOperator): prior terms to add the commutator to.
97    """
98    # In the case that both the creation and annihilation operators of the
99    # two actions pair, two new terms must be added.
100    if (one_body_action_a[0][0] == one_body_action_b[1][0] and
101            one_body_action_b[0][0] == one_body_action_a[1][0]):
102        new_one_body_action_a = ((one_body_action_a[0][0], 1),
103                                 (one_body_action_a[0][0], 0))
104        new_one_body_action_b = ((one_body_action_b[0][0], 1),
105                                 (one_body_action_b[0][0], 0))
106
107        prior_terms.terms[new_one_body_action_a] = (
108            prior_terms.terms.get(new_one_body_action_a, 0.0) + coefficient)
109        prior_terms.terms[new_one_body_action_b] = (
110            prior_terms.terms.get(new_one_body_action_b, 0.0) - coefficient)
111
112    # A single pairing causes the mixed action a[0]^ b[1] to be added
113    elif one_body_action_a[1][0] == one_body_action_b[0][0]:
114        action_ab = ((one_body_action_a[0][0], 1), (one_body_action_b[1][0], 0))
115
116        prior_terms.terms[action_ab] = (prior_terms.terms.get(action_ab, 0.0) +
117                                        coefficient)
118
119    # The other single pairing adds the mixed action b[0]^ a[1]
120    elif one_body_action_a[0][0] == one_body_action_b[1][0]:
121        action_ba = ((one_body_action_b[0][0], 1), (one_body_action_a[1][0], 0))
122
123        prior_terms.terms[action_ba] = (prior_terms.terms.get(action_ba, 0.0) -
124                                        coefficient)
125
126
127def _commutator_one_body_with_two_body(action_a, action_b, coefficient,
128                                       prior_terms):
129    """Compute commutator of action-specified one- and two-body operators.
130
131    Args:
132        action_a, action_b (tuple): single terms, one one-body and the other
133            two-body, from normal-ordered FermionOperators. It does not matter
134            which is one- or two-body so long as only one of each appears.
135        coefficient (complex float): coefficient of the commutator.
136        prior_terms (FermionOperator): prior terms to add the commutator to.
137    """
138    # Determine which action is 1-body and which is 2-body.
139    # Label the creation and annihilation parts of the two terms.
140    if len(action_a) == 4 and len(action_b) == 2:
141        one_body_create = action_b[0][0]
142        one_body_annihilate = action_b[1][0]
143        two_body_create = (action_a[0][0], action_a[1][0])
144        two_body_annihilate = (action_a[2][0], action_a[3][0])
145        new_action = list(action_a)
146        # Flip coefficient because we reversed the commutator's arguments.
147        coefficient *= -1
148    else:
149        one_body_create = action_a[0][0]
150        one_body_annihilate = action_a[1][0]
151        two_body_create = (action_b[0][0], action_b[1][0])
152        two_body_annihilate = (action_b[2][0], action_b[3][0])
153        new_action = list(action_b)
154
155    # If both terms are composed of number operators, they commute.
156    if one_body_create == one_body_annihilate and (
157            two_body_create == two_body_annihilate):
158        return
159
160    # If the one-body annihilation is in the two-body creation parts
161    if one_body_annihilate in two_body_create:
162        new_coeff = coefficient
163        new_inner_action = list(new_action)
164
165        # Determine which creation part(s) of the one-body action to use
166        if one_body_annihilate == two_body_create[0]:
167            new_inner_action[0] = (one_body_create, 1)
168        elif one_body_annihilate == two_body_create[1]:
169            new_inner_action[1] = (one_body_create, 1)
170
171        # Normal order if necessary
172        if new_inner_action[0][0] < new_inner_action[1][0]:
173            new_inner_action[0], new_inner_action[1] = (new_inner_action[1],
174                                                        new_inner_action[0])
175            new_coeff *= -1
176
177        # Add the resulting term.
178        if new_inner_action[0][0] > new_inner_action[1][0]:
179            prior_terms.terms[tuple(new_inner_action)] = (
180                prior_terms.terms.get(tuple(new_inner_action), 0.0) + new_coeff)
181
182    # If the one-body creation is in the two-body annihilation parts
183    if one_body_create in two_body_annihilate:
184        new_coeff = -coefficient
185
186        # Determine which annihilation part(s) of the one-body action to sub in
187        if one_body_create == two_body_annihilate[0]:
188            new_action[2] = (one_body_annihilate, 0)
189        elif one_body_create == two_body_annihilate[1]:
190            new_action[3] = (one_body_annihilate, 0)
191
192        # Normal order if necessary
193        if new_action[2][0] < new_action[3][0]:
194            new_action[2], new_action[3] = new_action[3], new_action[2]
195            new_coeff *= -1
196
197        # Add the resulting term.
198        if new_action[2][0] > new_action[3][0]:
199            prior_terms.terms[tuple(new_action)] = (
200                prior_terms.terms.get(tuple(new_action), 0.0) + new_coeff)
201
202
203def _commutator_two_body_diagonal_with_two_body(diagonal_coulomb_action,
204                                                arbitrary_two_body_action,
205                                                coefficient, prior_terms):
206    """Compute the commutator of two two-body operators specified by actions.
207
208    Args:
209        diagonal_coulomb_action (tuple): single term of a diagonal Coulomb
210            FermionOperator (i^ j^ i j). Must be in normal-ordered form,
211            i.e. i > j.
212        arbitrary_two_body_action (tuple): arbitrary single term of a two-body
213            FermionOperator, in normal-ordered form, i.e. i^ j^ k l with
214            i > j, k > l.
215        coefficient (complex float): coefficient of the commutator.
216        prior_terms (FermionOperator): prior terms to add the commutator to.
217
218    Notes:
219        The function could be readily extended to the case of reversed input
220        arguments (where diagonal_coulomb_action is the arbitrary one, and
221        arbitrary_two_body_action is from a diagonal Coulomb FermionOperator);
222        however, the extra check slows it down without significantly increased
223        utility.
224    """
225    # Identify creation and annihilation parts of arbitrary_two_body_action.
226    arb_2bdy_create = (arbitrary_two_body_action[0][0],
227                       arbitrary_two_body_action[1][0])
228    arb_2bdy_annihilate = (arbitrary_two_body_action[2][0],
229                           arbitrary_two_body_action[3][0])
230
231    # The first two sub-cases cover when the creations and annihilations of
232    # diagonal_coulomb_action and arbitrary_two_body_action totally pair up.
233    if (diagonal_coulomb_action[2][0] == arbitrary_two_body_action[0][0] and
234            diagonal_coulomb_action[3][0] == arbitrary_two_body_action[1][0]):
235        prior_terms.terms[arbitrary_two_body_action] = (
236            prior_terms.terms.get(arbitrary_two_body_action, 0.0) - coefficient)
237
238    elif (diagonal_coulomb_action[0][0] == arbitrary_two_body_action[2][0] and
239          diagonal_coulomb_action[1][0] == arbitrary_two_body_action[3][0]):
240        prior_terms.terms[arbitrary_two_body_action] = (
241            prior_terms.terms.get(arbitrary_two_body_action, 0.0) + coefficient)
242
243    # Exactly one of diagonal_coulomb_action's creations matches one of
244    # arbitrary_two_body_action's annihilations.
245    elif diagonal_coulomb_action[0][0] in arb_2bdy_annihilate:
246        # Nothing gets added if there's an unbalanced double creation.
247        if diagonal_coulomb_action[1][0] in arb_2bdy_create or (
248                diagonal_coulomb_action[0][0] in arb_2bdy_create):
249            return
250
251        _add_three_body_term(arbitrary_two_body_action, coefficient,
252                             diagonal_coulomb_action[1][0], prior_terms)
253
254    elif diagonal_coulomb_action[1][0] in arb_2bdy_annihilate:
255        # Nothing gets added if there's an unbalanced double creation.
256        if diagonal_coulomb_action[0][0] in arb_2bdy_create or (
257                diagonal_coulomb_action[1][0] in arb_2bdy_create):
258            return
259
260        _add_three_body_term(arbitrary_two_body_action, coefficient,
261                             diagonal_coulomb_action[0][0], prior_terms)
262
263    elif diagonal_coulomb_action[0][0] in arb_2bdy_create:
264        _add_three_body_term(arbitrary_two_body_action, -coefficient,
265                             diagonal_coulomb_action[1][0], prior_terms)
266
267    elif diagonal_coulomb_action[1][0] in arb_2bdy_create:
268        _add_three_body_term(arbitrary_two_body_action, -coefficient,
269                             diagonal_coulomb_action[0][0], prior_terms)
270
271
272def _add_three_body_term(two_body_action, coefficient, mode, prior_terms):
273    new_action = list(two_body_action)
274
275    # Insert creation and annihilation operators into the two-body action.
276    new_action.insert(0, (mode, 1))
277    new_action.insert(3, (mode, 0))
278
279    # Normal order the creation operators of the new action.
280    # Each exchange in the action flips the sign of the coefficient.
281    if new_action[0][0] < new_action[1][0]:
282        new_action[0], new_action[1] = new_action[1], new_action[0]
283        coefficient *= -1
284        if new_action[1][0] < new_action[2][0]:
285            new_action[1], new_action[2] = new_action[2], new_action[1]
286            coefficient *= -1
287
288    # Normal order the annihilation operators of the new action.
289    # Each exchange in the action flips the sign of the coefficient.
290    if new_action[3][0] < new_action[4][0]:
291        new_action[3], new_action[4] = new_action[4], new_action[3]
292        coefficient *= -1
293        if new_action[4][0] < new_action[5][0]:
294            new_action[4], new_action[5] = new_action[5], new_action[4]
295            coefficient *= -1
296
297    # Add the new normal-ordered term to the prior terms.
298    prior_terms.terms[tuple(new_action)] = (
299        prior_terms.terms.get(tuple(new_action), 0.0) + coefficient)
300