Skip to content

Commit

Permalink
Merge branch 'master' into update-test-devices
Browse files Browse the repository at this point in the history
  • Loading branch information
andrijapau authored Nov 27, 2024
2 parents cfc1da5 + b8d0b91 commit 5cba9fe
Show file tree
Hide file tree
Showing 9 changed files with 1,317 additions and 3 deletions.
32 changes: 32 additions & 0 deletions doc/code/qml_bose.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
qml.bose
=========

Overview
--------

This module contains functions and classes for creating and manipulating bosonic operators.


BoseWord and BoseSentence
---------------------------

.. currentmodule:: pennylane.bose

.. autosummary::
:toctree: api

~BoseWord
~BoseSentence

Mapping to qubit operators
--------------------------

.. currentmodule:: pennylane.bose

.. autosummary::
:toctree: api

~binary_mapping
~unary_mapping
~christiansen_mapping

1 change: 1 addition & 0 deletions doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ PennyLane is **free** and **open source**, released under the Apache License, Ve
:hidden:

code/qml
code/qml_bose
code/qml_compiler
code/qml_data
code/qml_debugging
Expand Down
8 changes: 8 additions & 0 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,16 @@
* Added a second class `DefaultMixedNewAPI` to the `qml.devices.qubit_mixed` module, which is to be the replacement of legacy `DefaultMixed` which for now to hold the implementations of `preprocess` and `execute` methods.
[(#6607)](https://github.com/PennyLaneAI/pennylane/pull/6507)

* Added `christiansen_mapping()` function to map `BoseWord` and `BoseSentence` to qubit operators, using christiansen mapping.
[(#6623)](https://github.com/PennyLaneAI/pennylane/pull/6623)

* Added `unary_mapping()` function to map `BoseWord` and `BoseSentence` to qubit operators, using unary mapping.
[(#6576)](https://github.com/PennyLaneAI/pennylane/pull/6576)

* Added `binary_mapping()` function to map `BoseWord` and `BoseSentence` to qubit operators, using standard-binary mapping.
[(#6564)](https://github.com/PennyLaneAI/pennylane/pull/6564)


<h3>Improvements 🛠</h3>

* Raises a comprehensive error when using `qml.fourier.qnode_spectrum` with standard numpy
Expand Down Expand Up @@ -282,6 +289,7 @@ This release contains contributions from (in alphabetical order):
Shiwen An,
Astral Cai,
Yushao Chen,
Diksha Dhawan,
Pietropaolo Frisoni,
Austin Huang,
Korbinian Kottmann,
Expand Down
8 changes: 7 additions & 1 deletion pennylane/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,13 @@
parity_transform,
bravyi_kitaev,
)
from pennylane.bose import BoseSentence, BoseWord, binary_mapping
from pennylane.bose import (
BoseSentence,
BoseWord,
binary_mapping,
unary_mapping,
christiansen_mapping,
)
from pennylane.qchem import (
taper,
symmetry_generators,
Expand Down
2 changes: 1 addition & 1 deletion pennylane/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@
Version number (major.minor.patch[-label])
"""

__version__ = "0.40.0-dev22"
__version__ = "0.40.0-dev23"
2 changes: 1 addition & 1 deletion pennylane/bose/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@
"""A module containing utility functions and mappings for working with bosonic operators. """

from .bosonic import BoseSentence, BoseWord
from .bosonic_mapping import binary_mapping
from .bosonic_mapping import binary_mapping, christiansen_mapping, unary_mapping
238 changes: 238 additions & 0 deletions pennylane/bose/bosonic_mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# limitations under the License.
"""This module contains functions to map bosonic operators to qubit operators."""

from collections import defaultdict
from functools import singledispatch
from typing import Union

Expand Down Expand Up @@ -159,3 +160,240 @@ def _(bose_operator: BoseSentence, n_states, tol=None):
qubit_operator.simplify(tol=1e-16)

return qubit_operator


def unary_mapping(
bose_operator: Union[BoseWord, BoseSentence],
n_states: int = 2,
ps: bool = False,
wire_map: dict = None,
tol: float = None,
):
r"""Convert a bosonic operator to a qubit operator using the unary mapping.
The mapping procedure is described in `arXiv.1909.12847 <https://arxiv.org/abs/1909.12847>`_.
Args:
bose_operator(BoseWord, BoseSentence): the bosonic operator
n_states(int): maximum number of allowed bosonic states
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 states of
the Bose operator to qubit wires. If None, integers used to
label the bosonic states 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 = qml.bose.BoseWord({(0, 0): "+"})
>>> qml.unary_mapping(w, n_states=4)
0.25 * X(0) @ X(1)
+ -0.25j * X(0) @ Y(1)
+ 0.25j * Y(0) @ X(1)
+ (0.25+0j) * Y(0) @ Y(1)
+ 0.3535533905932738 * X(1) @ X(2)
+ -0.3535533905932738j * X(1) @ Y(2)
+ 0.3535533905932738j * Y(1) @ X(2)
+ (0.3535533905932738+0j) * Y(1) @ Y(2)
+ 0.4330127018922193 * X(2) @ X(3)
+ -0.4330127018922193j * X(2) @ Y(3)
+ 0.4330127018922193j * Y(2) @ X(3)
+ (0.4330127018922193+0j) * Y(2) @ Y(3)
"""

qubit_operator = _unary_mapping_dispatch(bose_operator, n_states, tol=tol)

wires = list(bose_operator.wires) or [0]
identity_wire = wires[0]
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


@singledispatch
def _unary_mapping_dispatch(bose_operator, n_states, ps=False, wires_map=None, tol=None):
"""Dispatches to appropriate function if bose_operator is a BoseWord or BoseSentence."""
raise ValueError(f"bose_operator must be a BoseWord or BoseSentence, got: {bose_operator}")


@_unary_mapping_dispatch.register
def _(bose_operator: BoseWord, n_states, tol=None):

if n_states < 2:
raise ValueError(
f"Number of allowed bosonic states cannot be less than 2, provided {n_states}."
)

creation = np.zeros((n_states, n_states))
for i in range(n_states - 1):
creation[i + 1, i] = np.sqrt(i + 1.0)

coeff_mat = {"+": creation, "-": creation.T}

qubit_operator = PauliSentence({PauliWord({}): 1.0})

ops_per_idx = defaultdict(list)

# Avoiding superfluous terms by taking the product of
# coefficient matrices.
for (_, b_idx), sign in bose_operator.items():
ops_per_idx[b_idx].append(sign)

for b_idx, signs in ops_per_idx.items():
coeff_mat_prod = np.eye(n_states)
for sign in signs:
coeff_mat_prod = np.dot(coeff_mat_prod, coeff_mat[sign])

op = PauliSentence()
sparse_coeffmat = np.nonzero(coeff_mat_prod)
for i, j in zip(*sparse_coeffmat):
coeff = coeff_mat_prod[i][j]

row = np.zeros(n_states)
row[i] = 1

col = np.zeros(n_states)
col[j] = 1

pauliOp = PauliSentence({PauliWord({}): 1.0})
for n in range(n_states):
if row[n] == 1 or col[n] == 1:
pauliOp @= _get_pauli_op(row[n], col[n], n + b_idx * n_states)
op += coeff * pauliOp
qubit_operator @= op

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])
qubit_operator.simplify(tol=1e-16)

return qubit_operator


@_unary_mapping_dispatch.register
def _(bose_operator: BoseSentence, n_states, tol=None):

qubit_operator = PauliSentence()

for bw, coeff in bose_operator.items():
bose_word_as_ps = unary_mapping(bw, n_states=n_states, ps=True)

for pw in bose_word_as_ps:
qubit_operator[pw] = qubit_operator[pw] + bose_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)

return qubit_operator


def christiansen_mapping(
bose_operator: Union[BoseWord, BoseSentence],
ps: bool = False,
wire_map: dict = None,
tol: float = None,
):
r"""Convert a bosonic operator to a qubit operator using the Christiansen mapping.
This mapping assumes that the maximum number of allowed bosonic states is 2 and works only for
Christiansen bosons defined in `J. Chem. Phys. 120, 2140 (2004)
<https://pubs.aip.org/aip/jcp/article-abstract/120/5/2140/534128/A-second-quantization-formulation-of-multimode?redirectedFrom=fulltext>`_.
The bosonic creation and annihilation operators are mapped to the Pauli operators as
.. math::
b^{\dagger}_0 = \left (\frac{X_0 - iY_0}{2} \right ), \:\: \text{...,} \:\:
b^{\dagger}_n = \frac{X_n - iY_n}{2},
and
.. math::
b_0 = \left (\frac{X_0 + iY_0}{2} \right ), \:\: \text{...,} \:\:
b_n = \frac{X_n + iY_n}{2},
where :math:`X`, :math:`Y`, and :math:`Z` are the Pauli operators.
Args:
bose_operator(BoseWord, BoseSentence): the bosonic operator
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 states of
the Bose operator to qubit wires. If None, integers used to
label the bosonic states 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.
"""

qubit_operator = _christiansen_mapping_dispatch(bose_operator, tol)

wires = list(bose_operator.wires) or [0]
identity_wire = wires[0]
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


@singledispatch
def _christiansen_mapping_dispatch(bose_operator, tol):
"""Dispatches to appropriate function if bose_operator is a BoseWord or BoseSentence."""
raise ValueError(f"bose_operator must be a BoseWord or BoseSentence, got: {bose_operator}")


@_christiansen_mapping_dispatch.register
def _(bose_operator: BoseWord, tol=None):

qubit_operator = PauliSentence({PauliWord({}): 1.0})

coeffs = {"+": -0.5j, "-": 0.5j}

for (_, b_idx), sign in bose_operator.items():

qubit_operator @= PauliSentence(
{
PauliWord({**{b_idx: "X"}}): 0.5,
PauliWord({**{b_idx: "Y"}}): 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])

qubit_operator.simplify(tol=1e-16)

return qubit_operator


@_christiansen_mapping_dispatch.register
def _(bose_operator: BoseSentence, tol=None):

qubit_operator = PauliSentence()

for bw, coeff in bose_operator.items():
bose_word_as_ps = christiansen_mapping(bw, ps=True)

for pw in bose_word_as_ps:
qubit_operator[pw] = qubit_operator[pw] + bose_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)

return qubit_operator
Loading

0 comments on commit 5cba9fe

Please sign in to comment.