Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Bravyi-Kitaev mapping #5390

Merged
merged 40 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
b43681b
[skip ci] add bravyi kitaev function
ddhawan11 Mar 15, 2024
1fe2065
added bravyi_kitaev function
ddhawan11 Mar 19, 2024
cd8ec3e
Added Bravyi-Kitaev mapping
ddhawan11 Mar 19, 2024
e4bc6b9
Merge branch 'master' into bravyi_kitaev_mapping
ddhawan11 Mar 19, 2024
cf9c67d
Bravyi-Kitaev mapping minor review
ddhawan11 Mar 19, 2024
574f8fd
Bravyi-Kitaev mapping variable names reformatted
ddhawan11 Apr 1, 2024
6a79e63
Update pennylane/fermi/conversion.py
ddhawan11 Apr 2, 2024
fcec3db
Update pennylane/fermi/conversion.py
ddhawan11 Apr 2, 2024
cffe8e9
Update pennylane/fermi/conversion.py
ddhawan11 Apr 2, 2024
4544c5c
Update pennylane/fermi/conversion.py
ddhawan11 Apr 2, 2024
84f6139
Update pennylane/fermi/conversion.py
ddhawan11 Apr 2, 2024
dd8cd8c
Update pennylane/fermi/conversion.py
ddhawan11 Apr 4, 2024
7336939
Update pennylane/fermi/conversion.py
ddhawan11 Apr 4, 2024
a497a09
Reviewed suggestions
ddhawan11 Apr 4, 2024
d03fda1
Merge branch 'master' into bravyi_kitaev_mapping
ddhawan11 Apr 4, 2024
7e01618
Update pennylane/fermi/conversion.py
ddhawan11 Apr 4, 2024
507bcf6
Update pennylane/fermi/conversion.py
ddhawan11 Apr 4, 2024
c7c91fd
Update pennylane/fermi/conversion.py
ddhawan11 Apr 4, 2024
07fabbe
Update pennylane/fermi/conversion.py
ddhawan11 Apr 4, 2024
657ffa9
Update pennylane/fermi/conversion.py
ddhawan11 Apr 4, 2024
0b48043
Merge branch 'master' into bravyi_kitaev_mapping
ddhawan11 Apr 4, 2024
11e8e70
Update pennylane/fermi/conversion.py
ddhawan11 Apr 5, 2024
a267c47
Update doc/releases/changelog-dev.md
ddhawan11 Apr 5, 2024
0ae5dc2
Update pennylane/fermi/conversion.py
ddhawan11 Apr 5, 2024
c04e3cb
Update pennylane/fermi/conversion.py
ddhawan11 Apr 5, 2024
3a204db
Documentation fix for mapping
ddhawan11 Apr 5, 2024
33f4662
Merge branch 'master' into bravyi_kitaev_mapping
ddhawan11 Apr 5, 2024
10fe7bf
Update doc/releases/changelog-dev.md
ddhawan11 Apr 8, 2024
97f6a33
Merge branch 'master' into bravyi_kitaev_mapping
ddhawan11 Apr 8, 2024
9942432
Review for Bravyi-Kitaev Mapping
ddhawan11 Apr 8, 2024
80e9152
Review for Bravyi-Kitaev Mapping
ddhawan11 Apr 8, 2024
ca1a6d4
Review for Bravyi-Kitaev Mapping
ddhawan11 Apr 8, 2024
e633da5
Bravyi-Kitaev mapping review
ddhawan11 Apr 10, 2024
8bce705
Merge branch 'master' into bravyi_kitaev_mapping
ddhawan11 Apr 10, 2024
3ae68cb
Fixed datatype bug with tolerance
ddhawan11 Apr 10, 2024
6f19839
Update doc/releases/changelog-dev.md
ddhawan11 Apr 11, 2024
a97ea6d
Merge branch 'master' into bravyi_kitaev_mapping
ddhawan11 Apr 11, 2024
637535c
Added examples using from_string
ddhawan11 Apr 11, 2024
c3e4144
Merge branch 'master' into bravyi_kitaev_mapping
ddhawan11 Apr 11, 2024
cec3d21
Merge branch 'master' into bravyi_kitaev_mapping
ddhawan11 Apr 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,22 @@

```


* Added new function `qml.bravyi_kitaev` to map fermionic Hamiltonians to qubit Hamiltonians.
[(#5390)](https://github.com/PennyLaneAI/pennylane/pull/5390)

```python
import pennylane as qml
fermi_ham = qml.fermi.from_string('0+ 1-')

qubit_ham = qml.bravyi_kitaev(fermi_ham, n=6)
```

```pycon
>>> print(qubit_ham)
-0.25j * Y(0.0) + (-0.25+0j) * X(0) @ Z(1.0) + (0.25+0j) * X(0.0) + 0.25j * Y(0) @ Z(1.0)
```

* A new class `qml.ops.LinearCombination` is introduced. In essence, this class is an updated equivalent of `qml.ops.Hamiltonian`
but for usage with new operator arithmetic.
[(#5216)](https://github.com/PennyLaneAI/pennylane/pull/5216)
Expand All @@ -150,7 +166,6 @@
>>> op.error(method="commutator")
SpectralNormError(6.166666666666668e-06)
```
ddhawan11 marked this conversation as resolved.
Show resolved Hide resolved

<h3>Improvements 🛠</h3>

* The `qml.is_commuting` function now accepts `Sum`, `SProd`, and `Prod` instances.
Expand Down Expand Up @@ -358,6 +373,7 @@ Mikhail Andrenkov,
Utkarsh Azad,
Gabriel Bottrill,
Astral Cai,
Diksha Dhawan,
Isaac De Vlugt,
Amintor Dusko,
Pietropaolo Frisoni,
Expand Down
8 changes: 7 additions & 1 deletion pennylane/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,13 @@
from pennylane.resource import specs
import pennylane.resource
import pennylane.qchem
from pennylane.fermi import FermiC, FermiA, jordan_wigner
from pennylane.fermi import (
FermiC,
FermiA,
jordan_wigner,
parity_transform,
bravyi_kitaev,
)
from pennylane.qchem import (
taper,
symmetry_generators,
Expand Down
2 changes: 1 addition & 1 deletion pennylane/fermi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@
fermionic operators. """


from .conversion import jordan_wigner, parity_transform
from .conversion import jordan_wigner, parity_transform, bravyi_kitaev
from .fermionic import FermiWord, FermiC, FermiA, FermiSentence, from_string
266 changes: 266 additions & 0 deletions pennylane/fermi/conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from functools import singledispatch
from typing import Union

import numpy as np
import pennylane as qml
from pennylane.operation import Operator
from pennylane.pauli import PauliSentence, PauliWord
Expand Down Expand Up @@ -145,6 +146,8 @@ def _(fermi_operator: FermiSentence, ps=False, wire_map=None, tol=None):
if tol is not None and abs(qml.math.imag(qubit_operator[pw])) <= tol:
qubit_operator[pw] = qml.math.real(qubit_operator[pw])

qubit_operator.simplify(tol=1e-16)

if not ps:
qubit_operator = qubit_operator.operation(wire_order=[identity_wire])

Expand Down Expand Up @@ -299,3 +302,266 @@ def _(fermi_operator: FermiSentence, n, ps=False, wire_map=None, tol=None):
return qubit_operator.map_wires(wire_map)

return qubit_operator


def bravyi_kitaev(
fermi_operator: Union[FermiWord, FermiSentence],
n: int,
ps: bool = False,
wire_map: dict = None,
tol: float = None,
) -> Union[Operator, PauliSentence]:
r"""Convert a fermionic operator to a qubit operator using the Bravyi-Kitaev mapping.

.. note::

Hamiltonians created with this mapping should be used with operators and states that are
compatible with the Bravyi-Kitaev basis.
trbromley marked this conversation as resolved.
Show resolved Hide resolved

In the Bravyi-Kitaev mapping, both occupation number and parity of the orbitals are stored non-locally.
In comparison, :func:`~.jordan_wigner` stores the occupation number locally while storing the parity non-locally and vice-versa for :func:`~.parity_transform`. In the Bravyi-Kitaev mapping, the fermionic creation and annihilation operators for even-labelled orbitals are mapped to the Pauli operators as
ddhawan11 marked this conversation as resolved.
Show resolved Hide resolved

.. math::

\begin{align*}
a^{\dagger}_0 &= \frac{1}{2} \left ( X_0 -iY_{0} \right ), \\\\
a^{\dagger}_n &= \frac{1}{2} \left ( X_{U(n)} \otimes X_n \otimes Z_{P(n)} -iX_{U(n)} \otimes Y_{n} \otimes Z_{P(n)}\right ), \\\\
\end{align*}

and

.. math::
\begin{align*}
a_0 &= \frac{1}{2} \left ( X_0 + iY_{0} \right ), \\\\
a_n &= \frac{1}{2} \left ( X_{U(n)} \otimes X_n \otimes Z_{P(n)} +iX_{U(n)} \otimes Y_{n} \otimes Z_{P(n)}\right ). \\\\
\end{align*}

Similarly, the fermionic creation and annihilation operators for odd-labelled orbitals are mapped to the Pauli operators as

.. math::
\begin{align*}
a^{\dagger}_n &= \frac{1}{2} \left ( X_{U(n)} \otimes X_n \otimes Z_{P(n)} -iX_{U(n)} \otimes Y_{n} \otimes Z_{R(n)}\right ), \\\\
\end{align*}

and

.. math::
\begin{align*}
a_n &= \frac{1}{2} \left ( X_{U(n)} \otimes X_n \otimes Z_{P(n)} +iX_{U(n)} \otimes Y_{n} \otimes Z_{R(n)}\right ). \\\\
ddhawan11 marked this conversation as resolved.
Show resolved Hide resolved
\end{align*}

where :math:`X`, :math:`Y`, and :math:`Z` are the Pauli operators, and :math:`U(n)`, :math:`P(n)` and :math:`R(n)` represent the update, parity and remainder sets, respectively [`arXiv:1812.02233 <https://arxiv.org/abs/1812.02233>`_].

Args:
fermi_operator(FermiWord, FermiSentence): the fermionic operator
n (int): number of qubits, i.e., spin orbitals in the system
ps (bool): whether to return the result as a PauliSentence instead of an
Operator. Defaults to False.
wire_map (dict): a dictionary defining how to map the orbitals of
the Fermi operator to qubit wires. If None, the integers used to
order the orbitals will be used as wire labels. Defaults to None.
tol (float): tolerance for discarding the imaginary part of the coefficients

Returns:
Union[PauliSentence, Operator]: a linear combination of qubit operators

**Example**

>>> w = FermiWord({(0, 0) : '+', (1, 1) : '-'})
ddhawan11 marked this conversation as resolved.
Show resolved Hide resolved
>>> bravyi_kitaev(w, n=6)
ddhawan11 marked this conversation as resolved.
Show resolved Hide resolved
(
-0.25j * Y(0)
+ (-0.25+0j) * (X(0) @ Z(1))
+ (0.25+0j) * X(0)
+ 0.25j * (Y(0) @ Z(1))
ddhawan11 marked this conversation as resolved.
Show resolved Hide resolved
)

>>> bravyi_kitaev(w, n=6, ps=True)
-0.25j * Y(0)
+ (-0.25+0j) * X(0) @ Z(1)
+ (0.25+0j) * X(0)
+ 0.25j * Y(0) @ Z(1)

>>> bravyi_kitaev(w, n=6, ps=True, wire_map={0: 2, 1: 3})
-0.25j * Y(2)
+ (-0.25+0j) * X(2) @ Z(3)
+ (0.25+0j) * X(2)
+ 0.25j * Y(2) @ Z(3)

"""
return _bravyi_kitaev_dispatch(fermi_operator, n, ps, wire_map, tol)


def _update_set(j, bin_range, n):
soranjh marked this conversation as resolved.
Show resolved Hide resolved
"""
Computes the update set of the j-th orbital in n qubits.

Args:
j (int) : the orbital index
bin_range (int) : Binary range for given number of qubits
n (int) : number of qubits

Returns:
numpy.ndarray: Array containing the update set
"""

indices = np.array([], dtype=int)
midpoint = int(bin_range / 2)
if bin_range % 2 != 0:
return indices

if j < midpoint:
indices = np.append(indices, np.append(bin_range - 1, _update_set(j, midpoint, n)))
else:
indices = np.append(indices, _update_set(j - midpoint, midpoint, n) + midpoint)

indices = np.array([u for u in indices if u < n])
return indices


def _parity_set(j, bin_range):
"""
Computes the parity set of the j-th orbital in n qubits.

Args:
j (int) : the orbital index
bin_range (int) : Binary range for given number of qubits

Returns:
numpy.ndarray: Array of qubits which determine the parity of qubit j
"""

indices = np.array([], dtype=int)
midpoint = int(bin_range / 2)
if bin_range % 2 != 0:
return indices

if j < midpoint:
indices = np.append(indices, _parity_set(j, midpoint))
else:
indices = np.append(
indices,
np.append(
_parity_set(j - midpoint, midpoint) + midpoint,
midpoint - 1,
),
)

return indices


def _flip_set(j, bin_range):
"""
Computes the flip set of the j-th orbital in n qubits.

Args:
j (int) : the orbital index
bin_range (int) : Binary range for given number of qubits
Returns:
numpy.ndarray: Array containing information if the phase of orbital j is same as qubit j.
"""

indices = np.array([])
midpoint = int(bin_range / 2)
if bin_range % 2 != 0:
return indices

if j < midpoint:
indices = np.append(indices, _flip_set(j, midpoint))
elif midpoint <= j < bin_range - 1:
indices = np.append(indices, _flip_set(j - midpoint, midpoint) + midpoint)
else:
indices = np.append(
np.append(indices, _flip_set(j - midpoint, midpoint) + midpoint),
midpoint - 1,
)
return indices


@singledispatch
def _bravyi_kitaev_dispatch(fermi_operator, n, ps, wire_map, tol):
"""Dispatches to appropriate function if fermi_operator is a FermiWord or FermiSentence."""
raise ValueError(f"fermi_operator must be a FermiWord or FermiSentence, got: {fermi_operator}")


@_bravyi_kitaev_dispatch.register
def _(fermi_operator: FermiWord, n, ps=False, wire_map=None, tol=None):
wires = list(fermi_operator.wires) or [0]
identity_wire = wires[0]

coeffs = {"+": -0.5j, "-": 0.5j}
qubit_operator = PauliSentence({PauliWord({}): 1.0}) # Identity PS to multiply PSs with

bin_range = int(2 ** np.ceil(np.log2(n)))

for (_, wire), sign in fermi_operator.items():
if wire >= n:
raise ValueError(
f"Can't create or annihilate a particle on qubit index {wire} for a system with only {n} qubits."
)

u_set = _update_set(wire, bin_range, n)
update_string = dict(zip(u_set, ["X"] * len(u_set)))

p_set = _parity_set(wire, bin_range)
parity_string = dict(zip(p_set, ["Z"] * len(p_set)))

if wire % 2 == 0:
qubit_operator @= PauliSentence(
{
PauliWord({**parity_string, **{wire: "X"}, **update_string}): 0.5,
PauliWord({**parity_string, **{wire: "Y"}, **update_string}): coeffs[sign],
}
)
else:
f_set = _flip_set(wire, bin_range)

r_set = np.setdiff1d(p_set, f_set)
remainder_string = dict(zip(r_set, ["Z"] * len(r_set)))

qubit_operator @= PauliSentence(
{
PauliWord({**parity_string, **{wire: "X"}, **update_string}): 0.5,
PauliWord({**remainder_string, **{wire: "Y"}, **update_string}): coeffs[sign],
}
)

for pw in qubit_operator:
if tol is not None and abs(qml.math.imag(qubit_operator[pw])) <= tol:
qubit_operator[pw] = qml.math.real(qubit_operator[pw])

if not ps:
# wire_order specifies wires to use for Identity (PauliWord({}))
qubit_operator = qubit_operator.operation(wire_order=[identity_wire])

if wire_map:
return qubit_operator.map_wires(wire_map)

return qubit_operator


@_bravyi_kitaev_dispatch.register
def _(fermi_operator: FermiSentence, n, ps=False, wire_map=None, tol=None):
wires = list(fermi_operator.wires) or [0]
identity_wire = wires[0]

qubit_operator = PauliSentence() # Empty PS as 0 operator to add Pws to

for fw, coeff in fermi_operator.items():
fermi_word_as_ps = parity_transform(fw, n, ps=True)

for pw in fermi_word_as_ps:
qubit_operator[pw] += fermi_word_as_ps[pw] * coeff

if tol is not None and abs(qml.math.imag(qubit_operator[pw])) <= tol:
qubit_operator[pw] = qml.math.real(qubit_operator[pw])

qubit_operator.simplify(tol=1e-16)
ddhawan11 marked this conversation as resolved.
Show resolved Hide resolved

if not ps:
qubit_operator = qubit_operator.operation(wire_order=[identity_wire])

if wire_map:
return qubit_operator.map_wires(wire_map)

return qubit_operator
Loading
Loading