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 Ternary Tree Mapper #1313

Open
wants to merge 39 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
7d1134d
Add MajoranaOp class
grossardt Nov 11, 2023
08e0983
Merge branch 'main' into addfeature-majoranaop-issue1257
grossardt Nov 12, 2023
812653e
Merge branch 'main' into addfeature-majoranaop-issue1257
grossardt Nov 14, 2023
922eca3
New style of MajoranaOp.terms(), some new tests
grossardt Nov 18, 2023
51633b0
Merge branch 'main' into addfeature-majoranaop-issue1257
grossardt Nov 18, 2023
29600dd
Merge branch 'main' into addfeature-majoranaop-issue1257
grossardt Nov 27, 2023
97625dd
Update majorana_op.py
grossardt Nov 27, 2023
e2e9803
Merge branch 'main' into addfeature-majoranaop-issue1257
grossardt Nov 27, 2023
4c84cec
Update majorana_op.py
grossardt Nov 27, 2023
700aa59
Some easier reviews done. More will follow.
grossardt Nov 28, 2023
0ea65bf
Update releasenotes/notes/add-majoranaop-1cbf9d4a1d4c264e.yaml
grossardt Nov 28, 2023
2e1dd95
Update releasenotes/notes/add-majoranaop-1cbf9d4a1d4c264e.yaml
grossardt Nov 28, 2023
8d3987d
Merge branch 'main' into addfeature-majoranaop-issue1257
grossardt Nov 28, 2023
60e6473
Update qiskit_nature/second_q/operators/majorana_op.py
grossardt Dec 1, 2023
48661fa
Merge branch 'main' into addfeature-majoranaop-issue1257
grossardt Dec 1, 2023
d72af53
Reviewed changes
grossardt Dec 1, 2023
f73a068
Merge branch 'main' into addfeature-majoranaop-issue1257
grossardt Dec 4, 2023
d23cb86
Implement ModeBasedMapper no caching
grossardt Dec 11, 2023
3e318ba
Added caching and release note
grossardt Dec 12, 2023
f38cd64
Merge branch 'main' into fix-improve-mappers-issue1289
grossardt Dec 12, 2023
3b837d5
Merge branch 'fix-improve-mappers-issue1289' into addfeature-ternaryt…
grossardt Dec 12, 2023
d268913
Update release note to pass spell check
grossardt Dec 12, 2023
61042e0
fix typing
grossardt Dec 12, 2023
1a4b2e0
fix typing for Python <3.10
grossardt Dec 12, 2023
1b43a10
Merge branch 'main' into addfeature-majoranaop-issue1257
grossardt Dec 13, 2023
72ffda7
final suggestions by mrossinek
grossardt Dec 13, 2023
d77c269
Merge branch 'fix-improve-mappers-issue1289' into addfeature-ternaryt…
grossardt Jan 27, 2024
bc4e8ad
Merge branch 'addfeature-majoranaop-issue1257' into addfeature-ternar…
grossardt Jan 27, 2024
89f9114
Add TernaryTreeMapper
grossardt Jan 28, 2024
0f9416c
Lint/spellcheck
grossardt Jan 28, 2024
0b83db0
Merge branch 'main' into addfeature-ternarytreemap-issue582
grossardt Jan 28, 2024
343da0e
Copyright years fixed
grossardt Jan 28, 2024
2744801
copyright years (missed one)
grossardt Jan 28, 2024
eae1e2d
Make pauli_table for TernaryTreeMapper compatible
grossardt Jan 28, 2024
23b2f45
Merge branch 'main' into addfeature-ternarytreemap-issue582
woodsp-ibm Feb 20, 2024
83eee12
Merge branch 'main' into addfeature-ternarytreemap-issue582
grossardt Feb 26, 2024
386a579
fixes deprecation to pass tests
grossardt Feb 26, 2024
22b8c26
Merge branch 'main' into addfeature-ternarytreemap-issue582
grossardt Apr 19, 2024
b6d9a26
Update mode_based_mapper.py
grossardt Apr 19, 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
9 changes: 9 additions & 0 deletions .pylintdict
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ jcf
jcp
jernigan
ji
jiang
jk
jl
jordan
Expand All @@ -291,6 +292,7 @@ json
jupyter
jw
kagome
kalev
kanav
ket
kitaev
Expand Down Expand Up @@ -361,6 +363,7 @@ moller
molssi
morse
mpl
mruczkiewicz
mul
multi
multigraph
Expand All @@ -381,6 +384,7 @@ neq
networkx
neuropeptide
neutron
neven
nicholas
nielsen
nisq
Expand Down Expand Up @@ -664,11 +668,16 @@ xcfun
xdata
xy
xyz
xzy
ydata
yx
yxz
yy
yzx
zeitschrift
zi
zmatrix
zsh
zxy
zyx
zz
10 changes: 10 additions & 0 deletions qiskit_nature/second_q/mappers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@
BravyiKitaevSuperFastMapper
JordanWignerMapper
ParityMapper
TernaryTreeMapper

**Note:** :class:`~qiskit_nature.second_q.mappers.TernaryTreeMapper` maps
:class:`~qiskit_nature.second_q.operators.MajoranaOp` to
:class:`~qiskit.quantum_info.SparsePauliOp`. In order to use it on a
:class:`~qiskit_nature.second_q.operators.FermionicOp`, convert to a
:class:`~qiskit_nature.second_q.operators.MajoranaOp` first using
:code:`MajoranaOp.from_fermionic_op()`.

**Interleaved Qubit-Ordering:** If you want to generate qubit operators where the alpha-spin and
beta-spin components are mapped to the qubit register in an interleaved (rather than the default
Expand Down Expand Up @@ -98,6 +106,7 @@
from .bravyi_kitaev_mapper import BravyiKitaevMapper
from .jordan_wigner_mapper import JordanWignerMapper
from .parity_mapper import ParityMapper
from .ternary_tree_mapper import TernaryTreeMapper
from .linear_mapper import LinearMapper
from .bosonic_linear_mapper import BosonicLinearMapper
from .bosonic_logarithmic_mapper import BosonicLogarithmicMapper
Expand All @@ -114,6 +123,7 @@
"DirectMapper",
"JordanWignerMapper",
"ParityMapper",
"TernaryTreeMapper",
"LinearMapper",
"BosonicLinearMapper",
"BosonicLogarithmicMapper",
Expand Down
2 changes: 1 addition & 1 deletion qiskit_nature/second_q/mappers/bosonic_linear_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ 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).
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).
Expand Down
2 changes: 1 addition & 1 deletion qiskit_nature/second_q/mappers/bosonic_mapper.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of a Qiskit project.
#
# (C) Copyright IBM 2023.
# (C) Copyright IBM 2023, 2024.
#
# 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
Expand Down
33 changes: 33 additions & 0 deletions qiskit_nature/second_q/mappers/majorana_mapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# This code is part of a Qiskit project.
#
# (C) Copyright IBM 2024.
#
# 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.

"""Majorana Mapper."""

from __future__ import annotations

from qiskit.quantum_info import SparsePauliOp

from qiskit_nature.second_q.operators import MajoranaOp

from .qubit_mapper import ListOrDictType, QubitMapper


class MajoranaMapper(QubitMapper):
"""Mapper of Majorana Operator to Qubit Operator"""

def map(
self,
second_q_ops: MajoranaOp | ListOrDictType[MajoranaOp],
*,
register_length: int | None = None,
) -> SparsePauliOp | ListOrDictType[SparsePauliOp]:
return super().map(second_q_ops, register_length=register_length)
11 changes: 5 additions & 6 deletions qiskit_nature/second_q/mappers/mode_based_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def sparse_pauli_operators(
# pylint: disable=unused-argument
"""Generates the :class:`.SparsePauliOp` terms.

This uses :meth:`.QubitMapper.pauli_table` to construct a list of operators used to
This uses :meth:`.pauli_table` to construct a list of operators used to
translate the second-quantization symbols into qubit operators.

Args:
Expand Down Expand Up @@ -113,22 +113,21 @@ def mode_based_mapping(
register_length = second_q_op.register_length

times_creation_op, times_annihilation_op = self.sparse_pauli_operators(register_length)
mapped_string_length = times_creation_op[0].num_qubits

# make sure ret_op_list is not empty by including a zero op
ret_op_list = [SparsePauliOp("I" * register_length, coeffs=[0])]
ret_op_list = [SparsePauliOp("I" * mapped_string_length, coeffs=[0])]

for terms, coeff in second_q_op.terms():
# 1. Initialize an operator list with the identity scaled by the `coeff`
ret_op = SparsePauliOp("I" * register_length, coeffs=np.array([coeff]))
ret_op = SparsePauliOp("I" * mapped_string_length, coeffs=np.array([coeff]))

# Go through the label and replace the fermion operators by their qubit-equivalent, then
# save the respective Pauli string in the pauli_str list.
for term in terms:
char = term[0]
if char == "":
break
position = int(term[1])
if char == "+":
if char in ("+", ""): # "" for MajoranaOp, creator = annihilator
ret_op = ret_op.compose(times_creation_op[position], front=True).simplify()
elif char == "-":
ret_op = ret_op.compose(times_annihilation_op[position], front=True).simplify()
Expand Down
127 changes: 127 additions & 0 deletions qiskit_nature/second_q/mappers/ternary_tree_mapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# This code is part of a Qiskit project.
#
# (C) Copyright IBM 2024.
#
# 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.

"""Ternary Tree Mapper."""

from __future__ import annotations

from functools import lru_cache

from qiskit.quantum_info.operators import SparsePauliOp

from .majorana_mapper import MajoranaMapper
from .mode_based_mapper import ModeBasedMapper, PauliType


class TernaryTreeMapper(MajoranaMapper, ModeBasedMapper):
"""Ternary Tree fermion-to-qubit mapping.

As described by Jiang, Kalev, Mruczkiewicz, and Neven, Quantum 4, 276 (2020),
preprint at `arXiv:1910.10746 <https://arxiv.org/abs/1910.10746>`_.

This is a mapper for :class:`~qiskit_nature.second_q.operators.MajoranaOp`.
For mapping :class:`~qiskit_nature.second_q.operators.FermionicOp` convert
to a Majorana operator first:

.. code-block::

from qiskit_nature.second_q.operators import FermionicOp, MajoranaOp
from qiskit_nature.second_q.mappers import TernaryTreeMapper
fermionic_op = FermionicOp(...)
majorana_op = MajoranaOp.from_fermionic_op(fermionic_op)
majorana_op = majorana_op.index_order().simplify()
pauli_op = TernaryTreeMapper().map(majorana_op)

"""

_pauli_priority: str = "ZXY"

def __init__(self, pauli_priority: str = "ZXY"):
"""
Use the Pauli priority argument (one of XYZ, XZY, YXZ, YZX, ZXY, ZYX) to influence which
Pauli operators appear most frequently in the Pauli strings. The default is 'ZXY', due to
the fact that the Z gate is usually the most directly supported gate.

Args:
pauli_priority (str) : Priority with which Pauli operators are assigned.

Raises:
ValueError: if pauli_priority is not one of XYZ, XZY, YXZ, YZX, ZXY, ZYX
"""
super().__init__()
self._pauli_priority = pauli_priority.upper()
if self._pauli_priority not in ("XYZ", "XZY", "YXZ", "YZX", "ZXY", "ZYX"):
raise ValueError("Pauli priority must be one of XYZ, XZY, YXZ, YZX, ZXY, ZYX")

def pauli_table(self, register_length: int) -> list[tuple[PauliType, PauliType]]:
"""This method is implemented for ``TernaryTreeMapper`` only for compatibility.
``TernaryTreeMapper.map`` only uses the ``sparse_pauli_operators`` method which
overrides the corresponding method in `ModeBasedMapper`.
"""
pauli_table = []
for pauli in self._pauli_table(self._pauli_priority, register_length)[1]:
# creator/annihilator are constructed as (real +/- imaginary) / 2
# for Majorana ops (self adjoint) we have imaginary = 0 and need
# to multiply the operators from _pauli_table by 2 to get the correct pre-factor
pauli_table.append((2 * pauli[0], SparsePauliOp([""])))
return pauli_table

def _pauli_string_length(self, register_length: int) -> int:
return self._pauli_table(self._pauli_priority, register_length)[0]

@staticmethod
@lru_cache(maxsize=32)
def _pauli_table(
pauli_priority: str, register_length: int
) -> tuple[int, list[tuple[PauliType]]]:
tree_height = 0
while 3 ** (tree_height + 1) <= register_length + 1:
tree_height += 1
add_nodes = register_length + 1 - 3**tree_height

pauli_x, pauli_y, pauli_z = tuple(pauli_priority)
pauli_list: list[tuple[str, list, int]] = [("", [], 1)]
qubit_index = 0
for _ in range(tree_height):
new_pauli_list = []
for paulis, qubits, coeff in pauli_list:
new_pauli_list.append((paulis + pauli_x, qubits + [qubit_index], coeff))
new_pauli_list.append((paulis + pauli_y, qubits + [qubit_index], coeff))
new_pauli_list.append((paulis + pauli_z, qubits + [qubit_index], coeff))
qubit_index += 1
pauli_list = new_pauli_list
while add_nodes > 0:
paulis, qubits, coeff = pauli_list.pop(0)
pauli_list.append((paulis + pauli_x, qubits + [qubit_index], coeff))
pauli_list.append((paulis + pauli_y, qubits + [qubit_index], coeff))
add_nodes -= 1
if add_nodes > 0:
pauli_list.append((paulis + pauli_z, qubits + [qubit_index], coeff))
add_nodes -= 1
qubit_index += 1

num_qubits = qubit_index
pauli_list.pop() # only 2n of the 2n+1 operators are independent
pauli_ops = [(SparsePauliOp.from_sparse_list([pauli], num_qubits),) for pauli in pauli_list]
return num_qubits, pauli_ops

def sparse_pauli_operators(
self, register_length: int
) -> tuple[list[SparsePauliOp], list[SparsePauliOp]]:
times_creation_annihiliation_op = []

for paulis in self._pauli_table(self._pauli_priority, register_length)[1]:
# For Majorana ops (self adjoint) pauli_table is a list of 1-element tuples
creation_op = SparsePauliOp(paulis[0], coeffs=[1])
times_creation_annihiliation_op.append(creation_op)

return (times_creation_annihiliation_op, times_creation_annihiliation_op)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
features:
- |
Adds a new mapper class, :class:`~qiskit_nature.second_q.mappers.TernaryTreeMapper`
along with a new parent class :class:`~qiskit_nature.second_q.mappers.MajoranaMapper`
implementing the Ternary Tree based mapping algorithm
(cf. `arXiv:1910.10746 <https://arxiv.org/abs/1910.10746>`_) that maps a `MajoranaOp`
to a `SparsePauliOp`.
Loading