Skip to content

Commit

Permalink
Add Unary mapping for bosonic operators (#6576)
Browse files Browse the repository at this point in the history
**Context:**
Added Unary mapping

**Description of the Change:**
Added functions to map bosonic operators to qubit form using unary
mapping.

**Benefits:**

**Possible Drawbacks:**

**Related GitHub Issues:**
[sc-72640]

---------

Co-authored-by: Austin Huang <[email protected]>
Co-authored-by: Austin Huang <[email protected]>
Co-authored-by: soranjh <[email protected]>
Co-authored-by: Diego <[email protected]>
  • Loading branch information
5 people authored Nov 26, 2024
1 parent 6c44a57 commit a672a9b
Show file tree
Hide file tree
Showing 7 changed files with 816 additions and 2 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


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
4 changes: 4 additions & 0 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,13 @@
* 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 `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
7 changes: 6 additions & 1 deletion pennylane/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,12 @@
parity_transform,
bravyi_kitaev,
)
from pennylane.bose import BoseSentence, BoseWord, binary_mapping
from pennylane.bose import (
BoseSentence,
BoseWord,
binary_mapping,
unary_mapping,
)
from pennylane.qchem import (
taper,
symmetry_generators,
Expand Down
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 unary_mapping, binary_mapping
135 changes: 135 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,137 @@ 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
Loading

0 comments on commit a672a9b

Please sign in to comment.