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

Bosonic operators #1085

Merged
merged 49 commits into from
May 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
358c7b9
Created bosonic operator file. Some notes on the code
ftroisi Mar 2, 2023
5b169f9
modified ordering functions using commutator instead of anti-commutator
ftroisi Mar 2, 2023
ccc882f
Created simplify method
ftroisi Mar 2, 2023
7eaae1f
Created test class for bosonic operator
ftroisi Mar 3, 2023
a6b6374
Moved normal_order and index_order before simplify. Fixed the expecte…
ftroisi Mar 3, 2023
4f42c8d
Fixed tests. Hermicity test is still missing
ftroisi Mar 3, 2023
07bcdd1
Improved algorithm for simplify. Now calls normal order as first oper…
ftroisi Mar 7, 2023
1fb0269
fixed is_hermitian method
ftroisi Mar 7, 2023
1963209
Addressed PR comments
ftroisi Mar 8, 2023
7efafc8
Pipelines fixes
ftroisi Mar 8, 2023
5314730
Created bosonic_linear_mapper class
ftroisi Mar 9, 2023
04b9cc9
Added some unit tests for bosonic linear mapper. Pipelines fixes
ftroisi Mar 13, 2023
ca76367
Removed trailing whitspace
ftroisi Apr 27, 2023
2fb1514
Fixed test case +_0 -_0 with truncation 2
ftroisi Apr 27, 2023
1dcae8a
Implemented test cases +_0 -_1 and -_1 +_0. Some CI fixes
ftroisi Apr 28, 2023
ef07ee6
Added test case +_0 +_0
ftroisi Apr 29, 2023
78911b3
Some CI fixes
ftroisi Apr 29, 2023
1556c21
Apply suggestions from code review
ftroisi May 15, 2023
c95e383
Added test for commutator relation
ftroisi May 15, 2023
071e6af
Added terms to pylintdict
ftroisi May 15, 2023
e975e5d
Updated some mapper tests
ftroisi May 15, 2023
0d8cdaa
Addressed some PR comments. Fixed final tests
ftroisi May 15, 2023
45c6ecf
Addressed PR comments for docstrings
ftroisi May 16, 2023
d083163
Further doc fixes
ftroisi May 16, 2023
b9ba104
Further CI fixes
ftroisi May 16, 2023
521e283
Added release note
ftroisi May 16, 2023
4287b10
Spell fix in release note
ftroisi May 16, 2023
95b088a
Merge branch 'main' into bosonic_operators
ftroisi May 17, 2023
5176d56
Added methods from_terms and _permute_terms
ftroisi May 17, 2023
a44f1af
Addressed PR comments
ftroisi May 17, 2023
5bad546
ran make black
ftroisi May 17, 2023
2944977
Added r in front of docstring
ftroisi May 17, 2023
6b4a0fd
Added reference for bosonic op truncation
ftroisi May 17, 2023
b83316b
Addressed PR comments
ftroisi May 19, 2023
3c7ec4d
Added mapper example in bosonicLinearMapper docString
ftroisi May 19, 2023
670b44a
Updated error message
ftroisi May 21, 2023
fa53e96
Renamed truncation property. Defined its setter
ftroisi May 22, 2023
c45fcf7
Apply suggestions for max_occupation setter
ftroisi May 22, 2023
71ae4fa
ran make black
ftroisi May 22, 2023
1254ac0
Removed unused import
ftroisi May 22, 2023
7e0b58c
Doc fixes
ftroisi May 22, 2023
8fb988e
Doc fix
ftroisi May 22, 2023
1cf574c
ran make black
ftroisi May 22, 2023
f7f9b3c
Doc fix
ftroisi May 22, 2023
80af128
ran make black
ftroisi May 22, 2023
eeb1c1a
Apply suggestions from code review
ftroisi May 22, 2023
8a1179c
Minor doc fix
ftroisi May 22, 2023
d246aab
Apply suggestions from code review
mrossinek May 23, 2023
11fe4f6
Update qiskit_nature/second_q/mappers/bosonic_mapper.py
mrossinek May 23, 2023
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
5 changes: 5 additions & 0 deletions .pylintdict
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ boolean
bools
bopes
boson
bosons
bosonic
bosonicop
bravyi
calcinfo
cargs
Expand Down Expand Up @@ -336,6 +338,7 @@ metadata
meth
methionine
microsoft
miessen
minao
mixin
miyazawa
Expand Down Expand Up @@ -521,9 +524,11 @@ sklearn
sl
slater
soham
somma
sp
sparsearray
sparselabelop
sparsepauliop
spinop
springer
spsa
Expand Down
16 changes: 15 additions & 1 deletion qiskit_nature/second_q/mappers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@

.. currentmodule:: qiskit_nature.second_q.mappers

The classes here are used to convert fermionic, vibrational and spin operators to qubit operators.
The classes here are used to convert fermionic, bosonic, vibrational and spin operators to qubit
operators.

.. autosummary::
:toctree: ../stubs/
Expand Down Expand Up @@ -50,6 +51,17 @@

InterleavedQubitMapper


BosonicOp Mappers
+++++++++++++++++++

.. autosummary::
:toctree: ../stubs/
:template: autosummary/class_with_inherited_members.rst
:nosignatures:

BosonicLinearMapper

VibrationalOp Mappers
+++++++++++++++++++++

Expand Down Expand Up @@ -102,6 +114,7 @@
from .jordan_wigner_mapper import JordanWignerMapper
from .parity_mapper import ParityMapper
from .linear_mapper import LinearMapper
from .bosonic_linear_mapper import BosonicLinearMapper
from .logarithmic_mapper import LogarithmicMapper
from .direct_mapper import DirectMapper
from .qubit_mapper import QubitMapper
Expand All @@ -116,6 +129,7 @@
"JordanWignerMapper",
"ParityMapper",
"LinearMapper",
"BosonicLinearMapper",
"LogarithmicMapper",
"QubitConverter",
"QubitMapper",
Expand Down
195 changes: 195 additions & 0 deletions qiskit_nature/second_q/mappers/bosonic_linear_mapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2023.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""The Linear Mapper for Bosons."""

from __future__ import annotations
import operator

from functools import reduce, lru_cache

import numpy as np

from qiskit.quantum_info import Pauli, SparsePauliOp
from qiskit_nature import settings

from qiskit_nature.second_q.operators import BosonicOp
from .bosonic_mapper import BosonicMapper


class BosonicLinearMapper(BosonicMapper):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am still wondering about this class name. For now we can leave this as is, but we may want to think about a naming convention going forward in order to disambiguate the existing LinearMapper (for SpinOps).

Nothing for this PR, I just wanted this noted down.

"""The Linear boson-to-qubit mapping.

This mapper generates a linear encoding of the Bosonic operator :math:`b_k^\\dagger, b_k` to qubit
operators (linear combinations of pauli strings).
In this linear encoding each bosonic mode is represented via :math:`n_k^{max} + 1` qubits, where
:math:`n_k^{max}` is the max occupation of the mode (meaning the number of states used in the
expansion of the mode, or equivalently the state at which the maximum excitation can take place).
The mode :math:`|k\\rangle` is then mapped to the occupation number vector
:math:`|0_{n_k^{max}}, 0_{n_k^{max} - 1},..., 0_{n_k + 1}, 1_{n_k}, 0_{n_k - 1},..., 0_{0_k}\\rangle`

It implements the formula in Section II.C of Reference [1]:

.. math::
b_k^\\dagger = \\sum_{n_k =0}^{n_k^{max}-1}(\\sqrt{n_k +1}\\sigma_{n_k}^{+}\\sigma_{n_k + 1}^{-})

from :math:`n_k = 0` to :math:`n_k^{max} + 1` where :math:`n_k^{max}` is the maximum occupation
(defined by the user).
In the following implementation, we explicit the operators :math:`\\sigma^+` and :math:`\\sigma^-`
with the Pauli matrices:

.. math::
\\sigma_{n_k}^+ := S_j^+ = 0.5 * (X_j + \\textit{i}Y_j)

\\sigma_{n_k}^- := S_j^- = 0.5 * (X_j - \\textit{i}Y_j)

The length of the qubit register is:

.. code-block:: python

BosonicOp.num_modes * (BosonicLinearMapper.max_occupation + 1)

To use this mapper one can for example:

.. code-block:: python

from qiskit_nature.second_q.mappers import BosonicLinearMapper
from qiskit_nature.second_q.operators import BosonicOp

mapper = BosonicLinearMapper(max_occupation=1)
qubit_op = mapper.map(BosonicOp({'+_0 -_0': 1}, num_modes=1))

.. note::
Since this mapper truncates the maximum occupation of a bosonic state as represented in the
qubit register, the commutation relation after the mapping differ from the standard ones.
Please refer to Section 4, equation 22 of Reference [2] for more details

mrossinek marked this conversation as resolved.
Show resolved Hide resolved
References:
[1] A. Miessen et al., Quantum algorithms for quantum dynamics: A performance study on the
spin-boson model, Phys. Rev. Research 3, 043212.
https://link.aps.org/doi/10.1103/PhysRevResearch.3.043212

[2] R. Somma et al., Quantum Simulations of Physics Problems, Arxiv
ftroisi marked this conversation as resolved.
Show resolved Hide resolved
https://doi.org/10.48550/arXiv.quant-ph/0304063

"""

def _map_single(
self, second_q_op: BosonicOp, *, register_length: int | None = None
) -> SparsePauliOp:
"""Maps a :class:`~qiskit_nature.second_q.operators.SparseLabelOp` to a``SparsePauliOp``.

Args:
second_q_op: the ``SparseLabelOp`` to be mapped.
register_length: when provided, this will be used to overwrite the ``register_length``
attribute of the operator being mapped. This is possible because the
``register_length`` is considered a lower bound in a ``SparseLabelOp``.

mrossinek marked this conversation as resolved.
Show resolved Hide resolved
Returns:
The qubit operator corresponding to the problem-Hamiltonian in the qubit space.

Raises:
NotImplementedError: when `qiskit_nature.settings.use_pauli_sum_op` is set to `True`.
This value is deprecated and, thus, not supported by this new implementation.
Set it to `False` in order to use this mapper.
"""
if settings.use_pauli_sum_op:
raise NotImplementedError(
"This class does not support the deprecated `settings.use_pauli_sum_op` value."
"Please set `settings.use_pauli_sum_op = False` in order to use this mapper."
)
if register_length is None:
register_length = second_q_op.num_modes

qubit_register_length = register_length * (self.max_occupation + 1)
# Create a Pauli operator, which we will fill in this method
pauli_op: list[SparsePauliOp] = []
# Then we loop over all the terms of the bosonic operator
for terms, coeff in second_q_op.terms():
# Then loop over each term (terms -> List[Tuple[string, int]])
bos_op_to_pauli_op = SparsePauliOp(["I" * qubit_register_length], coeffs=[1.0])
for op, idx in terms:
if op not in ("+", "-"):
break
pauli_expansion: list[SparsePauliOp] = []
# Now we are dealing with a single bosonic operator. We have to perform the linear mapper
for n_k in range(self.max_occupation):
prefactor = np.sqrt(n_k + 1) / 4.0
# Define the actual index in the qubit register. It is given by n_k plus the shift
# due to the mode onto which the operator is acting
register_index = n_k + idx * (self.max_occupation + 1)
# Now build the Pauli operators XX, XY, YX, YY, which arise from S_i^+ S_j^-
x_x, x_y, y_x, y_y = self._get_ij_pauli_matrix(
register_index, qubit_register_length
)

tmp_op = SparsePauliOp(x_x) + SparsePauliOp(y_y)
if op == "+":
tmp_op += -1j * SparsePauliOp(x_y) + 1j * SparsePauliOp(y_x)
else:
tmp_op += +1j * SparsePauliOp(x_y) - 1j * SparsePauliOp(y_x)
pauli_expansion.append(prefactor * tmp_op)
# Add the Pauli expansion for a single n_k to map of the bosonic operator
bos_op_to_pauli_op = reduce(operator.add, pauli_expansion).compose(
bos_op_to_pauli_op
)
# Add the map of the single boson op (e.g. +_0) to the map of the full bosonic operator
pauli_op.append(coeff * reduce(operator.add, bos_op_to_pauli_op.simplify()))

# return the lookup table for the transformed XYZI operators
bos_op_encoding = reduce(operator.add, pauli_op)
mrossinek marked this conversation as resolved.
Show resolved Hide resolved
return bos_op_encoding

@classmethod
@lru_cache(maxsize=32)
def _get_ij_pauli_matrix(
cls, register_index: int, register_length: int
) -> tuple[Pauli, Pauli, Pauli, Pauli]:
"""This method builds the Qiskit Pauli operators of the operators XX, YY, XY and YX

Args:
register_index: the index of the qubit register where the mapped operator should be placed.
register_length: the length of the qubit register.

Returns:
Four Pauli operators that represent XX, XY, YX and YY at the specified index in the
current qubit register.
"""
# Define recurrent variables
prefix_zeros = [0] * register_index
suffix_zeros = [0] * (register_length - 2 - register_index)
# Build the Pauli strings
x_x = Pauli(
(
[0] * register_length,
prefix_zeros + [1, 1] + suffix_zeros,
)
)
x_y = Pauli(
(
prefix_zeros + [0, 1] + suffix_zeros,
prefix_zeros + [1, 1] + suffix_zeros,
)
)
y_x = Pauli(
(
prefix_zeros + [1, 0] + suffix_zeros,
prefix_zeros + [1, 1] + suffix_zeros,
)
)
y_y = Pauli(
(
prefix_zeros + [1, 1] + suffix_zeros,
prefix_zeros + [1, 1] + suffix_zeros,
)
)
return x_x, x_y, y_x, y_y
61 changes: 61 additions & 0 deletions qiskit_nature/second_q/mappers/bosonic_mapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2023.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Bosonic Mapper."""

from __future__ import annotations

from qiskit.quantum_info import SparsePauliOp

from qiskit_nature.second_q.operators import BosonicOp

from .qubit_mapper import ListOrDictType, QubitMapper


class BosonicMapper(QubitMapper):
"""Mapper of Bosonic Operator to Qubit Operator

The following attributes can be read and updated once the ``BosonicMapper`` object
has been constructed.

"""

def __init__(self, max_occupation: int) -> None:
"""
Args:
max_occupation: defines the excitation space of the k-th bosonic state. Together with the
number of modes required to represent the bosonic operator, it defines the minimum length
of the qubit register. The minimum value is 1.
"""
super().__init__()
self.max_occupation = max_occupation

@property
def max_occupation(self) -> int:
"""The maximum occupation of any bosonic state."""
return self._max_occupation

@max_occupation.setter
def max_occupation(self, max_occupation: int) -> None:
if max_occupation < 1:
raise ValueError(
f"The maximum occupation must be at least 1, and not {max_occupation}."
)
self._max_occupation = max_occupation

def map(
self,
second_q_ops: BosonicOp | ListOrDictType[BosonicOp],
*,
register_length: int | None = None,
) -> SparsePauliOp | ListOrDictType[SparsePauliOp]:
return super().map(second_q_ops, register_length=register_length)
3 changes: 3 additions & 0 deletions qiskit_nature/second_q/operators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

ElectronicIntegrals
FermionicOp
BosonicOp
SparseLabelOp
SpinOp
VibrationalOp
Expand All @@ -43,6 +44,7 @@

from .electronic_integrals import ElectronicIntegrals
from .fermionic_op import FermionicOp
from .bosonic_op import BosonicOp
from .spin_op import SpinOp
from .vibrational_op import VibrationalOp
from .vibrational_integrals import VibrationalIntegrals
Expand All @@ -53,6 +55,7 @@
__all__ = [
"ElectronicIntegrals",
"FermionicOp",
"BosonicOp",
"SpinOp",
"VibrationalOp",
"VibrationalIntegrals",
Expand Down
Loading