Source code for mpqp.core.instruction.measurement.pauli_string

"""Observables can be defined using linear combinations of Pauli operators,
these are called "Pauli strings". In ``mpqp``, a :class:`PauliString` is a 
linear combination of :class:`PauliStringMonomial` which are themselves 
combinations (tensor products) of :class:`PauliStringAtom`. :class:`PauliString` 
can be added, subtracted and tensored together, as well as multiplied by scalars."""

from __future__ import annotations

from copy import deepcopy
from functools import reduce
from numbers import Real
from operator import matmul, mul
from typing import TYPE_CHECKING, Any, Optional, Union

import numpy as np
import numpy.typing as npt

FixedReal = Union[Real, float]
from mpqp.core.languages import Language
from mpqp.tools.generics import Matrix
from mpqp.tools.maths import atol, rtol

if TYPE_CHECKING:
    from braket.circuits.observables import Observable as BraketObservable
    from braket.circuits.observables import Sum as BraketSum
    from braket.circuits.observables import TensorProduct
    from cirq.circuits.circuit import Circuit as CirqCircuit
    from cirq.ops.gate_operation import GateOperation as CirqGateOperation
    from cirq.ops.linear_combinations import PauliSum as CirqPauliSum
    from cirq.ops.pauli_string import PauliString as CirqPauliString
    from cirq.ops.raw_types import Qid
    from qat.core.wrappers.observable import Term
    from qiskit.quantum_info import SparsePauliOp


[docs]class PauliString: """Represents a Pauli string, a linear combination of Pauli monomials. Note that as a user of our library, you would most likely never need to directly call this class. Instead, we advise you to build the Pauli strings you need from the atom we provide (see the example bellow). Args: monomials : List of Pauli monomials. Example: >>> from mpqp.measures import I, X, Y, Z >>> I @ Z + 2 * Y @ I + X @ Z 1*I@Z + 2*Y@I + 1*X@Z Note: Pauli atoms are named ``I``, ``X``, ``Y``, and ``Z``. If you have conflicts with the gates of the same name, you could: - Rename the Pauli atoms: .. code-block:: python from mpqp.measures import X as Pauli_X, Y as Pauli_Y ps = Pauli_X + Pauli_Y/2 - Import the Pauli atoms directly from the module: .. code-block:: python from mpqp.measures import pauli_string ps = pauli_string.X + pauli_string.Y/2 """ def __init__(self, monomials: Optional[list["PauliStringMonomial"]] = None): self._monomials: list[PauliStringMonomial] = [] if monomials is not None: for mono in monomials: if isinstance(mono, PauliStringAtom): mono = PauliStringMonomial(1, [mono]) self._monomials.append(mono) for mono in self._monomials: if mono.nb_qubits != self.monomials[0].nb_qubits: raise ValueError( f"Non homogeneous sizes for given PauliStrings: {monomials}" ) @property def monomials(self) -> list[PauliStringMonomial]: """Monomials of the PauliString.""" return self._monomials @property def nb_qubits(self) -> int: """Number of qubits associated with the PauliString.""" return 0 if len(self._monomials) == 0 else self._monomials[0].nb_qubits def __str__(self): return " + ".join(map(str, self.round().simplify().sort_monomials()._monomials)) def __repr__(self): return " + ".join(map(str, self._monomials)) def __pos__(self) -> "PauliString": return deepcopy(self) def __neg__(self) -> "PauliString": return -1 * self def __iadd__(self, other: "PauliString") -> "PauliString": for mono in other.monomials: if ( len(self._monomials) != 0 and mono.nb_qubits != self._monomials[0].nb_qubits ): raise ValueError( f"Non homogeneous sizes for given PauliStrings: {(self, other)}" ) self._monomials.extend(deepcopy(other.monomials)) return self def __add__(self, other: "PauliString") -> "PauliString": res = deepcopy(self) res += other return res def __isub__(self, other: "PauliString") -> "PauliString": self += -1 * other return self def __sub__(self, other: "PauliString") -> "PauliString": return self + (-1) * other def __imul__(self, other: FixedReal) -> "PauliString": for i, mono in enumerate(self._monomials): if isinstance(mono, PauliStringAtom): self.monomials[i] = PauliStringMonomial(atoms=[mono]) self.monomials[i] *= other return self def __mul__(self, other: FixedReal) -> "PauliString": res = deepcopy(self) res *= other return res def __rmul__(self, other: FixedReal) -> "PauliString": return self * other def __itruediv__(self, other: FixedReal) -> "PauliString": self *= 1 / other # pyright: ignore[reportOperatorIssue] return self def __truediv__(self, other: FixedReal) -> "PauliString": return self * (1 / other) # pyright: ignore[reportOperatorIssue] def __imatmul__(self, other: "PauliString") -> "PauliString": self._monomials = [ mono for s_mono in self.monomials for mono in (s_mono @ other).monomials ] return self def __matmul__(self, other: "PauliString") -> "PauliString": res = deepcopy(self) res @= other return res def __eq__(self, other: object) -> bool: if not isinstance(other, PauliString): return False return self.to_dict() == other.to_dict()
[docs] def simplify(self, inplace: bool = False) -> PauliString: """Simplifies the Pauli string by combining identical terms and removing terms with null coefficients. Args: inplace: Indicates if ``self`` should be updated in addition of a new Pauli string being returned. Returns: The simplified version of the Pauli string. Example: >>> from mpqp.measures import I, X, Y, Z >>> (I @ I - 2 *I @ I + Z @ I - Z @ I).simplify() -1*I@I """ res = PauliString() for unique_mono_atoms in {tuple(mono.atoms) for mono in self.monomials}: coef = float( sum( [ mono.coef for mono in self.monomials if mono.atoms == list(unique_mono_atoms) ] ).real ) if coef == int(coef): coef = int(coef) if coef != 0: res.monomials.append(PauliStringMonomial(coef, list(unique_mono_atoms))) if len(res.monomials) == 0: res.monomials.append( PauliStringMonomial(0, [I for _ in range(self.nb_qubits)]) ) if inplace: self._monomials = res.monomials return res
[docs] def round(self, max_digits: int = 4) -> PauliString: """Rounds the coefficients of the PauliString to a specified number of decimal. Args: max_digits: Number of decimal places to round the coefficients to. Returns: The Pauli string with coefficients rounded to the specified number of decimals. Example: >>> from mpqp.measures import I, X, Y, Z >>> ps = 0.6875 * I @ I + 0.1275 * I @ Z >>> ps.round(1) 0.7*I@I + 0.1*I@Z """ res = PauliString() for mono in self.monomials: coef = float(np.round(float(mono.coef.real), max_digits)) if coef == int(coef): coef = int(coef) if coef != 0: res.monomials.append(PauliStringMonomial(coef, mono.atoms)) if len(res.monomials) == 0: res.monomials.append( PauliStringMonomial(0, [I for _ in range(self.nb_qubits)]) ) return res
[docs] def sort_monomials(self) -> PauliString: """Creates a new Pauli string with the same monomials but sorted in monomial alphabetical ascending order (and the coefficients are not taken into account). Returns: The Pauli string with its monomials sorted. Example: >>> from mpqp.measures import I, X, Y, Z >>> (2 * I @ Z + .5 * I @ X + X @ Y).sort_monomials() 0.5*I@X + 2*I@Z + 1*X@Y """ sorted_monomials = sorted( self.monomials, key=lambda m: tuple(str(atom) for atom in m.atoms) ) return PauliString(sorted_monomials)
[docs] def to_matrix(self) -> Matrix: """Converts the PauliString to a matrix representation. Returns: Matrix representation of the Pauli string. Example: >>> from mpqp.measures import I, X, Y, Z >>> pprint((I + Z).to_matrix()) [[2, 0], [0, 0]] """ self = self.simplify() return sum( map(lambda m: m.to_matrix(), self.monomials), start=np.zeros((2**self.nb_qubits, 2**self.nb_qubits), dtype=np.complex64), )
[docs] @staticmethod def from_matrix(matrix: Matrix) -> PauliString: """Constructs a PauliString from a matrix. Args: matrix: Matrix from which the PauliString is generated. Returns: Pauli string decomposition of the matrix in parameter. Raises: ValueError: If the input matrix is not square or its dimensions are not a power of 2. Example: >>> PauliString.from_matrix(np.array([[0, 1], [1, 2]])) 1.0*I + 1.0*X + -1.0*Z """ if matrix.shape[0] != matrix.shape[1]: raise ValueError("Input matrix must be square.") num_qubits = int(np.log2(matrix.shape[0])) if 2**num_qubits != matrix.shape[0]: raise ValueError("Matrix dimensions must be a power of 2.") # Return the ordered Pauli basis for the n-qubit Pauli basis. pauli_1q = [PauliStringMonomial(1, [atom]) for atom in [I, X, Y, Z]] basis = pauli_1q for _ in range(num_qubits - 1): basis = [p1 @ p2 for p1 in basis for p2 in pauli_1q] pauli_list = PauliString() for i, mat in enumerate(basis): coeff = (np.trace(mat.to_matrix().dot(matrix)) / (2**num_qubits)).real if not np.isclose(coeff, 0, atol=atol, rtol=rtol): mono = basis[i] * coeff pauli_list += mono if len(pauli_list.monomials) == 0: pauli_list.monomials.append( PauliStringMonomial(0, [I for _ in range(num_qubits)]) ) return pauli_list
@staticmethod def _get_dimension_cirq_pauli( pauli: Union[CirqPauliSum, CirqPauliString, CirqGateOperation] ): from cirq.ops.gate_operation import GateOperation as CirqGateOperation from cirq.ops.linear_combinations import PauliSum as CirqPauliSum from cirq.ops.pauli_string import PauliString as CirqPauliString dimension = 0 if isinstance(pauli, CirqPauliSum): for term in pauli: for qubit, _ in term.items(): nb_qubits = ( int(qubit.x) if hasattr(qubit, "x") else int(qubit.name.split("_")[1]) ) dimension = max(dimension, nb_qubits + 1) elif isinstance(pauli, CirqPauliString): for qubit in pauli.qubits: nb_qubits = ( int(qubit.x) if hasattr(qubit, "x") else int(qubit.name.split("_")[1]) ) dimension = max(dimension, nb_qubits + 1) elif isinstance(pauli, CirqGateOperation): for line_qubit in pauli._qubits: nb_qubits = int(line_qubit.x) dimension = max(dimension, nb_qubits + 1) return dimension @staticmethod def _from_cirq( pauli: Union[CirqPauliSum, CirqPauliString, CirqGateOperation], min_dimension: int = 1, ) -> PauliString: from cirq.ops.gate_operation import GateOperation as CirqGateOperation from cirq.ops.identity import I as Cirq_I from cirq.ops.linear_combinations import PauliSum as CirqPauliSum from cirq.ops.pauli_gates import X as Cirq_X from cirq.ops.pauli_gates import Y as Cirq_Y from cirq.ops.pauli_gates import Z as Cirq_Z from cirq.ops.pauli_string import PauliString as CirqPauliString ps_mapping = {Cirq_X: X, Cirq_Y: Y, Cirq_Z: Z, Cirq_I: I} num_qubits = max(PauliString._get_dimension_cirq_pauli(pauli), min_dimension) pauli_string = PauliString() def process_term(term: CirqPauliString, pauli_string: PauliString): coef = term.coefficient.real monomial = [I] * num_qubits for qubit, op in term.items(): index = ( int(qubit.x) if hasattr(qubit, "x") else int(qubit.name.split("_")[1]) ) monomial[index] = ps_mapping[op] pauli_string += PauliStringMonomial(coef, monomial) if isinstance(pauli, CirqPauliSum): for term in pauli: process_term(term, pauli_string) elif isinstance(pauli, CirqPauliString): process_term(pauli, pauli_string) elif isinstance(pauli, CirqGateOperation): monomial = [I] * num_qubits for line_qubit in pauli._qubits: index = int(line_qubit.x) monomial[index] = ps_mapping[pauli._gate] pauli_string += PauliStringMonomial(1, monomial) return pauli_string @staticmethod def _from_qiskit( pauli: SparsePauliOp, ) -> PauliString: pauli_string = PauliString() for pauli_str, coef in pauli.to_list(): monomial = PauliStringMonomial() for atom in pauli_str: monomial = _pauli_atom_dict[atom] @ monomial monomial *= coef pauli_string += monomial return pauli_string @staticmethod def _from_braket( pauli: BraketObservable, ) -> PauliString: from braket.circuits.observables import I as Braket_I from braket.circuits.observables import Sum as BraketSum from braket.circuits.observables import TensorProduct from braket.circuits.observables import X as Braket_X from braket.circuits.observables import Y as Braket_Y from braket.circuits.observables import Z as Braket_Z def tensor_product_to_pauli_sting(pauli: TensorProduct): monomial = PauliStringMonomial() for atom in pauli.factors: monomial @= _pauli_atom_dict[atom.ascii_symbols[0]] monomial *= pauli.coefficient return monomial if isinstance(pauli, BraketSum): pauli_str = PauliString() for tensor_product in pauli.summands: if isinstance(tensor_product, TensorProduct): pauli_str += tensor_product_to_pauli_sting(tensor_product) elif isinstance( tensor_product, (Braket_I, Braket_X, Braket_Y, Braket_Z) ): pauli_str += _pauli_atom_dict[tensor_product.ascii_symbols[0]] else: raise NotImplementedError( f"Unsupported input type: {type(tensor_product)}." ) return pauli_str elif isinstance(pauli, TensorProduct): return tensor_product_to_pauli_sting(pauli) elif isinstance(pauli, (Braket_I, Braket_X, Braket_Y, Braket_Z)): return _pauli_atom_dict[pauli.ascii_symbols[0]] else: raise NotImplementedError(f"Unsupported input type: {type(pauli)}.") @staticmethod def _from_my_qml( pauli: Term, min_dimension: int = 0, ) -> PauliStringMonomial: min_dimension = ( max(min_dimension, max(pauli.qbits) + 1) if len(pauli.qbits) > 0 else min_dimension ) monomial = [I] * min_dimension for index, atom in enumerate(pauli.op): monomial[pauli.qbits[index]] = _pauli_atom_dict[atom] return PauliStringMonomial(pauli.coeff, monomial)
[docs] @staticmethod def from_other_language( pauli: Union[ SparsePauliOp, BraketObservable, TensorProduct, list[Term], Term, CirqPauliSum, CirqPauliString, list[CirqPauliString], CirqGateOperation, ], min_dimension: int = 1, ) -> PauliString | list[PauliString]: """Convert pauli objects from other quantum SDKs to :class:`PauliString`. args: pauli: The pauli object(s) to be converted. min_dimension: Minimal dimension of the resulting Pauli string. Returns: The converted :class:`PauliString`. If the input is a list, the output will be a list of :class:`PauliString`. Examples: >>> from cirq import LineQubit, PauliSum, X as Cirq_X, Y as Cirq_Y, Z as Cirq_Z >>> a, b, c = LineQubit.range(3) >>> cirq_ps = 0.5 * Cirq_Z(a) * 0.5 * Cirq_Y(b) + 2 * Cirq_X(c) >>> PauliString.from_other_language(cirq_ps) 0.25*Z@Y@I + 2.0*I@I@X >>> from braket.circuits.observables import ( ... Sum as BraketSum, ... I as Braket_I, ... X as Braket_X, ... Y as Braket_Y, ... Z as Braket_Z, ... ) >>> braket_ps = 0.25 * Braket_Z() @ Braket_Y() @ Braket_I() + 2 * Braket_I() @ Braket_I() @ Braket_X() >>> PauliString.from_other_language(braket_ps) 0.25*Z@Y@I + 2*I@I@X >>> from qiskit.quantum_info import SparsePauliOp >>> qiskit_ps = SparsePauliOp(["IYZ", "XII"], coeffs=[0.25 + 0.0j, 2.0 + 0.0j]) >>> PauliString.from_other_language(qiskit_ps) (0.25+0j)*Z@Y@I + (2+0j)*I@I@X >>> from qat.core import Term >>> my_qml_ps = [Term(0.25, "ZY", [0, 1]), Term(2, "X", [2])] >>> PauliString.from_other_language(my_qml_ps) 0.25*Z@Y@I + 2*I@I@X """ if isinstance(pauli, list) and any( not isinstance(p, type(pauli[0])) for p in pauli ): raise ValueError( "Cannot parse non-homogeneous types when `pauli` is a `list`." ) from braket.circuits.observables import Observable as BraketObservable from cirq.ops.gate_operation import GateOperation as CirqGateOperation from cirq.ops.linear_combinations import PauliSum as CirqPauliSum from cirq.ops.pauli_string import PauliString as CirqPauliString from qat.core.wrappers.observable import Term from qiskit.quantum_info import SparsePauliOp if isinstance(pauli, SparsePauliOp): return PauliString._from_qiskit(pauli) elif isinstance(pauli, BraketObservable): return PauliString._from_braket(pauli) elif isinstance(pauli, Term): return PauliString._from_my_qml(pauli) elif isinstance(pauli, list) and isinstance(pauli[0], Term): for term in pauli: min_dimension = ( max(max(term.qbits) + 1, min_dimension) if len(term.qbits) > 0 else min_dimension ) pauli_string = PauliString() for term in pauli: pauli_string += PauliString._from_my_qml(term, min_dimension) return pauli_string elif isinstance(pauli, (CirqPauliSum, CirqPauliString, CirqGateOperation)): return PauliString._from_cirq(pauli, min_dimension) elif isinstance(pauli, list) and isinstance(pauli[0], CirqPauliString): min_dimension = max( max(map(PauliString._get_dimension_cirq_pauli, pauli)), min_dimension ) return [ PauliString._from_cirq(pauli_mono, min_dimension) for pauli_mono in pauli ] raise NotImplementedError(f"Unsupported input type: {type(pauli)}.")
[docs] def to_other_language( self, language: Language, circuit: Optional[CirqCircuit] = None ) -> Union[ SparsePauliOp, BraketSum, list[Term], Term, CirqPauliSum, CirqPauliString, list[CirqPauliString], ]: """Converts the pauli string to pauli string of another quantum programming language. Args: language: The target programming language. circuit: The Cirq circuit associated with the pauli string (required for ``cirq``). Returns: Depends on the target language. Example: >>> from mpqp.measures import I, X, Y, Z >>> ps = X @ X @ I + I @ Y @ I + I @ I @ Z >>> print(ps.to_other_language(Language.CIRQ)) 1.000*X(q(0))*X(q(1))+1.000*Y(q(1))+1.000*Z(q(2)) >>> for term in ps.to_other_language(Language.MY_QLM): ... print(term.op, term.qbits) XX [0, 1] Y [1] Z [2] >>> print(ps.to_other_language(Language.QISKIT)) SparsePauliOp(['IXX', 'IYI', 'ZII'], coeffs=[1.+0.j, 1.+0.j, 1.+0.j]) >>> for tensor in ps.to_other_language(Language.BRAKET).summands: ... print(tensor.coefficient, "".join(a.name for a in tensor.factors)) 1 XXI 1 IYI 1 IIZ """ if language == Language.QISKIT: from qiskit.quantum_info import SparsePauliOp pauli_string = [] pauli_string_coef = [] for mono in self.monomials: pauli_string.append( "".join(atom.label for atom in reversed(mono.atoms)) ) pauli_string_coef.append(mono.coef) return SparsePauliOp(pauli_string, np.array(pauli_string_coef)) elif language == Language.MY_QLM: return [mono.to_other_language(language) for mono in self.monomials] elif language == Language.BRAKET: pauli_string = None for mono in self.monomials: braket_mono = mono.to_other_language(Language.BRAKET) pauli_string = ( pauli_string + braket_mono if pauli_string is not None else braket_mono ) return pauli_string elif language == Language.CIRQ: cirq_pauli_string = None for monomial in self.monomials: cirq_monomial = monomial.to_other_language(language, circuit) cirq_pauli_string = ( cirq_monomial if cirq_pauli_string is None else cirq_pauli_string + cirq_monomial ) return cirq_pauli_string else: raise NotImplementedError(f"Unsupported language: {language}")
[docs] def to_dict(self) -> dict[str, float]: """Converts the Pauli string to a dictionary representation with the keys being the Pauli monomials and the values the corresponding coefficients. Returns: Mapping representing the Pauli string. Example: >>> from mpqp.measures import I, X, Y, Z >>> (1 * I @ Z + 2 * I @ I).to_dict() {'II': 2, 'IZ': 1} """ self = self.simplify() dict = {} for mono in self.monomials: atom_str = "" for atom in mono.atoms: atom_str += str(atom) if atom_str not in dict: dict[atom_str] = mono.coef else: dict[atom_str] += mono.coef return {k: dict[k] for k in sorted(dict)}
def __hash__(self): monomials_as_tuples = tuple( tuple((atom.label for atom in mono.atoms) for mono in self.monomials) ) return hash(monomials_as_tuples)
[docs]class PauliStringMonomial(PauliString): """Represents a monomial in a Pauli string, consisting of a coefficient and a list of PauliStringAtom objects. Args: coef: The coefficient of the monomial. atoms: The list of PauliStringAtom objects forming the monomial. """ def __init__( self, coef: Real | float = 1, atoms: Optional[list["PauliStringAtom"]] = None ): self.coef = coef """Coefficient of the monomial.""" self.atoms = [] if atoms is None else atoms """The list of atoms in the monomial.""" @property def nb_qubits(self) -> int: return len(self.atoms) @property def monomials(self) -> list["PauliStringMonomial"]: return [PauliStringMonomial(self.coef, self.atoms)] def __str__(self): return f"{self.coef}*{'@'.join(map(str,self.atoms))}" def __repr__(self): return str(self)
[docs] def to_matrix(self) -> Matrix: return ( reduce( np.kron, map(lambda a: a.to_matrix(), self.atoms), np.eye(1, dtype=np.complex64).tolist(), ) * self.coef )
def __iadd__(self, other: "PauliString"): for mono in other.monomials: if ( len(self.monomials) != 0 and mono.nb_qubits != self.monomials[0].nb_qubits ): raise ValueError( f"Non homogeneous sizes for given PauliStrings: {(self, other)}" ) res = PauliString([self]) res.monomials.extend(deepcopy(other.monomials)) return res def __add__(self, other: "PauliString") -> PauliString: res = deepcopy(self) res += other return res def __imul__(self, other: FixedReal) -> PauliStringMonomial: self.coef *= other return self def __mul__(self, other: FixedReal) -> PauliStringMonomial: res = deepcopy(self) res *= other return res def __itruediv__(self, other: FixedReal) -> PauliStringMonomial: self.coef /= other return self def __truediv__(self, other: FixedReal) -> PauliStringMonomial: res = deepcopy(self) res /= other return res def __imatmul__(self, other: PauliString) -> PauliString: if isinstance(other, PauliStringAtom): self.atoms.append(other) return self elif isinstance(other, PauliStringMonomial): self.coef *= other.coef self.atoms.extend(other.atoms) return self else: res = deepcopy(other) res._monomials = [ mono for s_mono in self.monomials for mono in (other @ s_mono)._monomials ] return res def __matmul__(self, other: PauliString): res = deepcopy(self) res @= other return res
[docs] def simplify(self, inplace: bool = False): return deepcopy(self)
def __eq__(self, other: object) -> bool: if isinstance(other, PauliStringMonomial): for a1, a2 in zip(self.atoms, other.atoms): if a1 != a2: return False return self.coef == other.coef return super().__eq__(other) def __hash__(self): atoms_as_tuples = tuple((atom.label for atom in self.atoms)) return hash(atoms_as_tuples)
[docs] def to_other_language( self, language: Language, circuit: Optional[CirqCircuit] = None ): if language == Language.QISKIT: from qiskit.quantum_info import SparsePauliOp pauli_mono_str = "".join(atom.label for atom in reversed(self.atoms)) return SparsePauliOp(pauli_mono_str, np.array(self.coef)) elif language == Language.MY_QLM: from qat.core.wrappers.observable import Term pauli_mono_str = "".join(atom.label for atom in self.atoms) return Term(self.coef, pauli_mono_str, list(range(len(pauli_mono_str)))) elif language == Language.BRAKET: braket_atoms: list[BraketObservable] = [ atom.to_other_language(Language.BRAKET) for atom in self.atoms ] # pyright: ignore[reportAssignmentType] return self.coef * reduce(matmul, braket_atoms) elif language == Language.CIRQ: from cirq.devices.line_qubit import LineQubit from cirq.ops.identity import IdentityGate as CirqI from cirq.ops.pauli_gates import Pauli as CirqPauli all_qubits = ( LineQubit.range(self.nb_qubits) if circuit is None else sorted( set( qubit for moment in circuit for op in moment.operations for qubit in op.qubits ) ) ) cirq_atoms: list[Union[CirqPauli, CirqI]] = [ atom.to_other_language(Language.CIRQ, target=all_qubits[index]) for index, atom in enumerate(self.atoms) ] return reduce(mul, cirq_atoms) * self.coef else: raise NotImplementedError(f"Unsupported language: {language}")
[docs]class PauliStringAtom(PauliStringMonomial): """Represents a single Pauli operator acting on a qubit in a Pauli string. Args: label: The label representing the Pauli operator. matrix: The matrix representation of the Pauli operator. Raises: RuntimeError: New atoms cannot be created, you should use the available ones. Note: All the atoms are already initialized. Available atoms are (``I``, ``X``, ``Y``, ``Z``). """ __is_mutable = True def __init__(self, label: str, matrix: npt.NDArray[np.complex64]): if _allow_atom_creation: self.label = label self.matrix = matrix self.__is_mutable = False else: raise RuntimeError( "New atoms cannot be created, just use the given `I`, `X`, `Y` " "and `Z`" ) @property def nb_qubits(self) -> int: return 1 @property def atoms(self): """Atoms present. (needed for upward compatibility with :class:`PauliStringMonomial`)""" return [self] @property def coef(self): """Coefficient of the monomial.""" return 1 @property def monomials(self): return [PauliStringMonomial(self.coef, [a for a in self.atoms])] def __setattr__(self, name: str, value: Any): if not self.__is_mutable: raise AttributeError("This object is immutable") super().__setattr__(name, value) def __str__(self): return self.label def __repr__(self): return str(self) def __itruediv__(self, other: FixedReal) -> PauliStringMonomial: self = self / other return self def __truediv__(self, other: FixedReal) -> PauliStringMonomial: return PauliStringMonomial( 1 / other, [self] # pyright: ignore[reportArgumentType] ) def __imul__(self, other: FixedReal) -> PauliStringMonomial: self = self * other return self def __mul__(self, other: FixedReal) -> PauliStringMonomial: return PauliStringMonomial(other, [self]) def __rmul__(self, other: FixedReal) -> PauliStringMonomial: return PauliStringMonomial(other, [self]) def __imatmul__(self, other: PauliString) -> PauliString: res = ( PauliStringMonomial(1, [other]) if isinstance(other, PauliStringAtom) else deepcopy(other) ) if isinstance(res, PauliStringMonomial): res.atoms.insert(0, self) else: for i, mono in enumerate(res.monomials): res.monomials[i] = PauliStringMonomial(mono.coef, mono.atoms) res.monomials[i].atoms.insert(0, self) return res def __matmul__(self, other: PauliString): res = deepcopy(self) res @= other return res
[docs] def to_matrix(self) -> npt.NDArray[np.complex64]: return self.matrix
def __eq__(self, other: object) -> bool: if isinstance(other, PauliStringAtom): return self.label == other.label else: return super().__eq__(other) def __hash__(self): return hash(self.label)
[docs] def to_other_language( self, language: Language, circuit: Optional[CirqCircuit] = None, target: Optional[Qid] = None, ): if language == Language.QISKIT: from qiskit.quantum_info import SparsePauliOp return SparsePauliOp(self.label) elif language == Language.MY_QLM: from qat.core.wrappers.observable import Term return Term(1.0, self.label, [0]) elif language == Language.BRAKET: from braket.circuits.observables import I as Braket_I from braket.circuits.observables import X as Braket_X from braket.circuits.observables import Y as Braket_Y from braket.circuits.observables import Z as Braket_Z pauli_gate_map = { "I": Braket_I(), "X": Braket_X(), "Y": Braket_Y(), "Z": Braket_Z(), } return pauli_gate_map[self.label] elif language == Language.CIRQ: from cirq.devices.line_qubit import LineQubit from cirq.ops.identity import I as Cirq_I from cirq.ops.pauli_gates import X as Cirq_X from cirq.ops.pauli_gates import Y as Cirq_Y from cirq.ops.pauli_gates import Z as Cirq_Z pauli_gate_map = { "I": Cirq_I, "X": Cirq_X, "Y": Cirq_Y, "Z": Cirq_Z, } return pauli_gate_map[self.label]( LineQubit(0) if target is None else target ) else: raise NotImplementedError(f"Unsupported language: {language}")
_allow_atom_creation = True I = PauliStringAtom("I", np.eye(2, dtype=np.complex64)) r"""Pauli-I atom representing the identity operator in a Pauli monomial or string. Matrix representation: `\begin{pmatrix}1&0\\0&1\end{pmatrix}` """ X = PauliStringAtom("X", 1 - np.eye(2, dtype=np.complex64)) r"""Pauli-X atom representing the X operator in a Pauli monomial or string. Matrix representation: `\begin{pmatrix}0&1\\1&0\end{pmatrix}` """ Y = PauliStringAtom("Y", np.fliplr(np.diag([1j, -1j]))) r"""Pauli-Y atom representing the Y operator in a Pauli monomial or string. Matrix representation: `\begin{pmatrix}0&-i\\i&0\end{pmatrix}` """ Z = PauliStringAtom("Z", np.diag([1, -1])) r"""Pauli-Z atom representing the Z operator in a Pauli monomial or string. Matrix representation: `\begin{pmatrix}1&0\\0&-1\end{pmatrix}` """ _pauli_atom_dict = {"I": I, "X": X, "Y": Y, "Z": Z} _allow_atom_creation = False