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