-
Notifications
You must be signed in to change notification settings - Fork 209
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
Bosonic operators #1085
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 5b169f9
modified ordering functions using commutator instead of anti-commutator
ftroisi ccc882f
Created simplify method
ftroisi 7eaae1f
Created test class for bosonic operator
ftroisi a6b6374
Moved normal_order and index_order before simplify. Fixed the expecte…
ftroisi 4f42c8d
Fixed tests. Hermicity test is still missing
ftroisi 07bcdd1
Improved algorithm for simplify. Now calls normal order as first oper…
ftroisi 1fb0269
fixed is_hermitian method
ftroisi 1963209
Addressed PR comments
ftroisi 7efafc8
Pipelines fixes
ftroisi 5314730
Created bosonic_linear_mapper class
ftroisi 04b9cc9
Added some unit tests for bosonic linear mapper. Pipelines fixes
ftroisi ca76367
Removed trailing whitspace
ftroisi 2fb1514
Fixed test case +_0 -_0 with truncation 2
ftroisi 1dcae8a
Implemented test cases +_0 -_1 and -_1 +_0. Some CI fixes
ftroisi ef07ee6
Added test case +_0 +_0
ftroisi 78911b3
Some CI fixes
ftroisi 1556c21
Apply suggestions from code review
ftroisi c95e383
Added test for commutator relation
ftroisi 071e6af
Added terms to pylintdict
ftroisi e975e5d
Updated some mapper tests
ftroisi 0d8cdaa
Addressed some PR comments. Fixed final tests
ftroisi 45c6ecf
Addressed PR comments for docstrings
ftroisi d083163
Further doc fixes
ftroisi b9ba104
Further CI fixes
ftroisi 521e283
Added release note
ftroisi 4287b10
Spell fix in release note
ftroisi 95b088a
Merge branch 'main' into bosonic_operators
ftroisi 5176d56
Added methods from_terms and _permute_terms
ftroisi a44f1af
Addressed PR comments
ftroisi 5bad546
ran make black
ftroisi 2944977
Added r in front of docstring
ftroisi 6b4a0fd
Added reference for bosonic op truncation
ftroisi b83316b
Addressed PR comments
ftroisi 3c7ec4d
Added mapper example in bosonicLinearMapper docString
ftroisi 670b44a
Updated error message
ftroisi fa53e96
Renamed truncation property. Defined its setter
ftroisi c45fcf7
Apply suggestions for max_occupation setter
ftroisi 71ae4fa
ran make black
ftroisi 1254ac0
Removed unused import
ftroisi 7e0b58c
Doc fixes
ftroisi 8fb988e
Doc fix
ftroisi 1cf574c
ran make black
ftroisi f7f9b3c
Doc fix
ftroisi 80af128
ran make black
ftroisi eeb1c1a
Apply suggestions from code review
ftroisi 8a1179c
Minor doc fix
ftroisi d246aab
Apply suggestions from code review
mrossinek 11fe4f6
Update qiskit_nature/second_q/mappers/bosonic_mapper.py
mrossinek File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
195 changes: 195 additions & 0 deletions
195
qiskit_nature/second_q/mappers/bosonic_linear_mapper.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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): | ||
"""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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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
(forSpinOp
s).Nothing for this PR, I just wanted this noted down.