Source code for qiskit_nature.second_q.mappers.linear_mapper

# This code is part of a Qiskit project.
#
# (C) Copyright IBM 2021, 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."""

from __future__ import annotations
import operator

from collections import defaultdict
from fractions import Fraction
from functools import reduce

import numpy as np

from qiskit.quantum_info import Pauli, SparsePauliOp

from qiskit_nature.second_q.operators import SpinOp
from .spin_mapper import SpinMapper


[docs]class LinearMapper(SpinMapper): """The Linear spin-to-qubit mapping.""" def _map_single( self, second_q_op: SpinOp, *, register_length: int | None = None ) -> SparsePauliOp: if register_length is None: register_length = second_q_op.register_length qubit_ops_list: list[SparsePauliOp] = [] # get linear encoding of the general spin matrices spinx, spiny, spinz, identity = self._linear_encoding(second_q_op.spin) ordered_op = second_q_op.index_order() char_map = {"X": spinx, "Y": spiny, "Z": spinz} for terms, coeff in ordered_op.terms(): mat = defaultdict(list) # type: dict[int, list] for op, idx in terms: if idx not in mat: mat[idx] = identity mat[idx] = mat[idx] @ char_map[op] operatorlist = [mat[i] if i in mat else identity for i in range(register_length)] # Now, we can tensor all operators in this list qubit_ops_list.append(coeff * reduce(operator.xor, reversed(operatorlist))) qubit_op = reduce(operator.add, qubit_ops_list) return qubit_op.simplify() def _linear_encoding(self, spin: Fraction | float) -> list[SparsePauliOp]: """ Generates a 'linear_encoding' of the spin S operators 'X', 'Y', 'Z' and 'identity' to qubit operators (linear combinations of pauli strings). In this 'linear_encoding' each individual spin S system is represented via 2S+1 qubits and the state |s> is mapped to the state |00...010..00>, where the s-th qubit is in state 1. Returns: The 4-element list of transformed spin S 'X', 'Y', 'Z' and 'identity' operators. I.e. spin_op_encoding[0]` corresponds to the linear combination of pauli strings needed to represent the embedded 'X' operator """ dspin = int(2 * spin + 1) nqubits = dspin # quick functions to generate a pauli with X / Y / Z at location `i` pauli_id = Pauli("I" * nqubits) def pauli_x(i): return Pauli("I" * i + "X" + "I" * (nqubits - i - 1)) def pauli_y(i): return Pauli("I" * i + "Y" + "I" * (nqubits - i - 1)) def pauli_z(i): return Pauli("I" * i + "Z" + "I" * (nqubits - i - 1)) # 1. build the non-diagonal X operator x_summands = [] for i, coeff in enumerate(np.diag(SpinOp.x(spin).to_matrix(), 1)): x_summands.append( coeff / 2.0 * SparsePauliOp(pauli_x(i).dot(pauli_x(i + 1))) + coeff / 2.0 * SparsePauliOp(pauli_y(i).dot(pauli_y(i + 1))) ) # 2. build the non-diagonal Y operator y_summands = [] for i, coeff in enumerate(np.diag(SpinOp.y(spin).to_matrix(), 1)): y_summands.append( -1j * coeff / 2.0 * SparsePauliOp(pauli_x(i).dot(pauli_y(i + 1))) + 1j * coeff / 2.0 * SparsePauliOp(pauli_y(i).dot(pauli_x(i + 1))) ) # 3. build the diagonal Z z_summands = [] for i, coeff in enumerate(np.diag(SpinOp.z(spin).to_matrix())): # get the first upper diagonal of coeff. z_summands.append( coeff / 2.0 * SparsePauliOp(pauli_z(i)) + coeff / 2.0 * SparsePauliOp(pauli_id) ) # return the lookup table for the transformed XYZI operators spin_op_encoding = [ reduce(operator.add, x_summands), reduce(operator.add, y_summands), reduce(operator.add, z_summands), SparsePauliOp(pauli_id), ] return spin_op_encoding