Skip to content

Commit

Permalink
Methods to commute terms in Fermi objects (#6196)
Browse files Browse the repository at this point in the history
This PR introduces the method `shift_operator` to the `FermiWord` class.
Given an initial position and a final position, the method will shift
the operator in the initial position to the final position, and apply
the fermion anti-commutator relations. `shift_operator` returns a
`FermiSentence` object since new terms and coefficients may be
introduced by the anti-commutator relations.

The following code
```
fw = FermiWord({(0, 0): '+', (1, 1): '-', (2, 2): '+'})
fw.shift_operator(2, 0)
```
produces `FermiSentence({FermiWord({(0, 2): '+', (1, 0): '+', (2, 1):
'-'}): 1})` which is the `FermiSentence` obtained by shifting the
operator in the 2nd position until it reaches the 0th position. That is
`a+(0) a(1) a+(2)` becomes `a+(2) a+(0) a(1)`.

---------

Co-authored-by: Austin Huang <65315367+austingmhuang@users.noreply.github.com>
Co-authored-by: Utkarsh <utkarshazad98@gmail.com>
Co-authored-by: soranjh <40344468+soranjh@users.noreply.github.com>
  • Loading branch information
4 people authored Sep 18, 2024
1 parent b52af2e commit 09ae791
Show file tree
Hide file tree
Showing 3 changed files with 207 additions and 0 deletions.
3 changes: 3 additions & 0 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
`from pennylane.capture.primitives import *`.
[(#6129)](https://github.com/PennyLaneAI/pennylane/pull/6129)

* `FermiWord` class now has a method to apply anti-commutator relations.
[(#6196)](https://github.com/PennyLaneAI/pennylane/pull/6196)

* `FermiWord` and `FermiSentence` classes now have methods to compute adjoints.
[(#6166)](https://github.com/PennyLaneAI/pennylane/pull/6166)

Expand Down
104 changes: 104 additions & 0 deletions pennylane/fermi/fermionic.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,110 @@ def to_mat(self, n_orbitals=None, format="dense", buffer_size=None):
wire_order=list(range(largest_order)), format=format, buffer_size=buffer_size
)

def shift_operator(self, initial_position, final_position):
r"""Shifts an operator in the FermiWord from ``initial_position`` to ``final_position`` by applying the fermionic anti-commutation relations.
There are three `anti-commutator relations <https://en.wikipedia.org/wiki/Creation_and_annihilation_operators#Creation_and_annihilation_operators_in_quantum_field_theories>`_:
.. math::
\left\{ a_i, a_j \right\} = 0, \quad \left\{ a^{\dagger}_i, a^{\dagger}_j \right\} = 0, \quad \left\{ a_i, a^{\dagger}_j \right\} = \delta_{ij},
where
.. math::
\left\{a_i, a_j \right\} = a_i a_j + a_j a_i,
and
.. math::
\delta_{ij} = \begin{cases} 1 & i = j \\ 0 & i \neq j \end{cases}.
Args:
initial_position (int): The position of the operator to be shifted.
final_position (int): The desired position of the operator.
Returns:
FermiSentence: The ``FermiSentence`` obtained after applying the anti-commutator relations.
Raises:
TypeError: if ``initial_position`` or ``final_position`` is not an integer
ValueError: if ``initial_position`` or ``final_position`` are outside the range ``[0, len(fermiword) - 1]``
where ``len(fermiword)`` is the number of operators in the FermiWord.
**Example**
>>> w = qml.fermi.FermiWord({(0, 0): '+', (1, 1): '-'})
>>> w.shift_operator(0, 1)
-1 * a(1) a⁺(0)
"""

if not isinstance(initial_position, int) or not isinstance(final_position, int):
raise TypeError("Positions must be integers.")

if initial_position < 0 or final_position < 0:
raise ValueError("Positions must be positive integers.")

if initial_position > len(self.sorted_dic) - 1 or final_position > len(self.sorted_dic) - 1:
raise ValueError("Positions are out of range.")

if initial_position == final_position:
return FermiSentence({self: 1})

fw = self
fs = FermiSentence({fw: 1})
delta = 1 if initial_position < final_position else -1
current = initial_position

while current != final_position:
indices = list(fw.sorted_dic.keys())
next = current + delta
curr_idx, curr_val = indices[current], fw[indices[current]]
next_idx, next_val = indices[next], fw[indices[next]]

# commuting identical terms
if curr_idx[1] == next_idx[1] and curr_val == next_val:
current += delta
continue

coeff = fs.pop(fw)

fw = dict(fw)
fw[(current, next_idx[1])] = next_val
fw[(next, curr_idx[1])] = curr_val

if curr_idx[1] != next_idx[1]:
del fw[curr_idx], fw[next_idx]

fw = FermiWord(fw)

# anti-commutator is 0
if curr_val == next_val or curr_idx[1] != next_idx[1]:
current += delta
fs += -coeff * fw
continue

# anti-commutator is 1
_min = min(current, next)
_max = max(current, next)
items = list(fw.sorted_dic.items())

left = FermiWord({(i, key[1]): value for i, (key, value) in enumerate(items[:_min])})
middle = FermiWord(
{(i, key[1]): value for i, (key, value) in enumerate(items[_min : _max + 1])}
)
right = FermiWord(
{(i, key[1]): value for i, (key, value) in enumerate(items[_max + 1 :])}
)

terms = left * (1 - middle) * right
fs += coeff * terms

current += delta

return fs


# pylint: disable=useless-super-delegation
class FermiSentence(dict):
Expand Down
100 changes: 100 additions & 0 deletions tests/fermi/test_fermionic.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,62 @@
fw7 = FermiWord({(0, 10): "-", (1, 30): "+", (2, 0): "-", (3, 400): "+"})
fw7_dag = FermiWord({(0, 400): "-", (1, 0): "+", (2, 30): "-", (3, 10): "+"})

fw8 = FermiWord({(0, 0): "-", (1, 1): "+"})
fw8c = FermiWord({(0, 1): "+", (1, 0): "-"})
fw8cs = FermiSentence({fw8c: -1})

fw9 = FermiWord({(0, 0): "-", (1, 1): "-"})
fw9c = FermiWord({(0, 1): "-", (1, 0): "-"})
fw9cs = FermiSentence({fw9c: -1})

fw10 = FermiWord({(0, 0): "+", (1, 1): "+"})
fw10c = FermiWord({(0, 1): "+", (1, 0): "+"})
fw10cs = FermiSentence({fw10c: -1})

fw11 = FermiWord({(0, 0): "-", (1, 0): "+"})
fw11c = FermiWord({(0, 0): "+", (1, 0): "-"})
fw11cs = 1 + FermiSentence({fw11c: -1})

fw12 = FermiWord({(0, 0): "+", (1, 0): "+"})
fw12c = FermiWord({(0, 0): "+", (1, 0): "+"})
fw12cs = FermiSentence({fw12c: 1})

fw13 = FermiWord({(0, 0): "-", (1, 0): "-"})
fw13c = FermiWord({(0, 0): "-", (1, 0): "-"})
fw13cs = FermiSentence({fw13c: 1})

fw14 = FermiWord({(0, 0): "+", (1, 0): "-"})
fw14c = FermiWord({(0, 0): "-", (1, 0): "+"})
fw14cs = 1 + FermiSentence({fw14c: -1})

fw15 = FermiWord({(0, 0): "-", (1, 1): "+", (2, 2): "+"})
fw15c = FermiWord({(0, 1): "+", (1, 0): "-", (2, 2): "+"})
fw15cs = FermiSentence({fw15c: -1})

fw16 = FermiWord({(0, 0): "-", (1, 1): "+", (2, 2): "-"})
fw16c = FermiWord({(0, 0): "-", (1, 2): "-", (2, 1): "+"})
fw16cs = FermiSentence({fw16c: -1})

fw17 = FermiWord({(0, 0): "-", (1, 0): "+", (2, 2): "-"})
fw17c1 = FermiWord({(0, 2): "-"})
fw17c2 = FermiWord({(0, 0): "+", (1, 0): "-", (2, 2): "-"})
fw17cs = fw17c1 - fw17c2

fw18 = FermiWord({(0, 0): "+", (1, 1): "+", (2, 2): "-", (3, 3): "-"})
fw18c = FermiWord({(0, 0): "+", (1, 3): "-", (2, 1): "+", (3, 2): "-"})
fw18cs = FermiSentence({fw18c: 1})

fw19 = FermiWord({(0, 0): "+", (1, 1): "+", (2, 2): "-", (3, 2): "+"})
fw19c1 = FermiWord({(0, 0): "+", (1, 1): "+"})
fw19c2 = FermiWord({(0, 2): "+", (1, 0): "+", (2, 1): "+", (3, 2): "-"})
fw19cs = FermiSentence({fw19c1: 1, fw19c2: -1})

fw20 = FermiWord({(0, 0): "-", (1, 0): "+", (2, 1): "-", (3, 0): "-", (4, 0): "+"})
fw20c1 = FermiWord({(0, 0): "-", (1, 0): "+", (2, 1): "-"})
fw20c2 = FermiWord({(0, 0): "+", (1, 1): "-", (2, 0): "-"})
fw20c3 = FermiWord({(0, 0): "+", (1, 0): "-", (2, 0): "+", (3, 1): "-", (4, 0): "-"})
fw20cs = fw20c1 + fw20c2 - fw20c3


class TestFermiWord:
def test_missing(self):
Expand Down Expand Up @@ -167,6 +223,41 @@ def test_to_mat_error(self):
with pytest.raises(ValueError, match="n_orbitals cannot be smaller than 2"):
fw1.to_mat(n_orbitals=1)

tup_fw_shift = (
(fw8, 0, 1, fw8cs),
(fw9, 0, 1, fw9cs),
(fw10, 0, 1, fw10cs),
(fw11, 0, 1, fw11cs),
(fw12, 0, 1, fw12cs),
(fw13, 0, 1, fw13cs),
(fw14, 0, 1, fw14cs),
(fw15, 0, 1, fw15cs),
(fw16, 1, 2, fw16cs),
(fw17, 0, 1, fw17cs),
(fw8, 0, 0, FermiSentence({fw8: 1})),
(fw8, 1, 0, fw8cs),
(fw11, 1, 0, fw11cs),
(fw18, 3, 1, fw18cs),
(fw19, 3, 0, fw19cs),
(fw20, 4, 0, fw20cs),
)

@pytest.mark.parametrize("fw, i, j, fs", tup_fw_shift)
def test_shift_operator(self, fw, i, j, fs):
"""Test that the shift_operator method correctly applies the anti-commutator relations."""
assert fw.shift_operator(i, j) == fs

def test_shift_operator_errors(self):
"""Test that the shift_operator method correctly raises exceptions."""
with pytest.raises(TypeError, match="Positions must be integers."):
fw8.shift_operator(0.5, 1)

with pytest.raises(ValueError, match="Positions must be positive integers."):
fw8.shift_operator(-1, 0)

with pytest.raises(ValueError, match="Positions are out of range."):
fw8.shift_operator(1, 2)

tup_fw_dag = (
(fw1, fw1_dag),
(fw2, fw2_dag),
Expand Down Expand Up @@ -588,6 +679,15 @@ def test_array_must_not_exceed_length_1(self, method_name):
}
)

fs8 = fw8 + fw9
fs8c = fw8 + fw9cs

fs9 = 1.3 * fw8 + (1.4 + 3.8j) * fw9
fs9c = 1.3 * fw8 + (1.4 + 3.8j) * fw9cs

fs10 = -1.3 * fw11 + 2.3 * fw9
fs10c = -1.3 * fw11cs + 2.3 * fw9


class TestFermiSentence:
def test_missing(self):
Expand Down

0 comments on commit 09ae791

Please sign in to comment.