Source code for qc_executor.qiskit.qiskit_operator
"""Qiskit native operator, compiled from the shared sparse Pauli representation."""
from __future__ import annotations
from collections import OrderedDict
from typing import Any, List
from qc_executor.base.operator_base import QuantumOperatorBase
from qc_executor.base.operator_ir import PauliIR
from qc_executor.qiskit._ir_bridge import pauli_ir_to_sparse_pauli_op, sparse_pauli_op_to_pauli_ir
from qc_executor.qiskit._param_binding import build_params_dict
[docs]
class QiskitOperator(QuantumOperatorBase):
"""An observable that compiles to Qiskit's ``SparsePauliOp``.
Built like any other operator -- ``QiskitOperator(["ZI"], [1.0])`` -- or
converted from an existing one with :meth:`from_quantum_operator`. The
``SparsePauliOp`` is produced on demand, so the whole inherited algebra
(:meth:`compose`, :meth:`adjoint`, :meth:`apply_layout`, ...) is available
and stays in this abstraction.
Args:
paulis: Pauli labels, qubit 0 leftmost.
coeffs: One coefficient per label.
num_qubits: Width, required only when no labels are given.
_ir: Adopt this representation instead of building one.
"""
def _build_native(self):
"""Compile the representation into a Qiskit ``SparsePauliOp``."""
return pauli_ir_to_sparse_pauli_op(self._ir)
[docs]
@classmethod
def from_quantum_operator(cls, operator: Any, **options: Any) -> "QiskitOperator":
"""Convert an operator, or import a ``SparsePauliOp`` passed directly.
Args:
operator: A :class:`~qc_executor.base.operator_base.QuantumOperatorBase`
or a raw ``qiskit.quantum_info.SparsePauliOp``.
``**options``: Accepted for interface parity; unused.
Returns:
The operator in this native form.
"""
if not isinstance(operator, QuantumOperatorBase):
return cls(_ir=sparse_pauli_op_to_pauli_ir(operator))
return super().from_quantum_operator(operator, **options) # type: ignore[return-value]
# ------------------------------------------------------------------
# Properties
# ------------------------------------------------------------------
@property
def qiskit_operator(self):
"""The underlying Qiskit ``SparsePauliOp``."""
return self.native
@property
def free_parameters(self) -> set:
"""The Qiskit parameter objects appearing in the compiled operator."""
return set(self.native.parameters)
@property
def parameter_dimensions(self) -> dict:
"""Number of elements used from each Qiskit parameter vector."""
dimensions: OrderedDict = OrderedDict()
for parameter in self.native.parameters:
name = parameter.vector.name if hasattr(parameter, "vector") else parameter.name
dimensions[name] = dimensions.get(name, 0) + 1
return dict(dimensions)
@property
def parameter_names(self) -> List[str]:
"""Names of the Qiskit parameter vectors used by the operator."""
return list(self.parameter_dimensions)
# ------------------------------------------------------------------
# Operations
# ------------------------------------------------------------------
[docs]
def bind_parameters(self, parameter_values: dict):
"""Bind values and return the resulting **Qiskit** operator.
Distinct from :meth:`assign_parameters`, which stays in this
abstraction and returns a :class:`QiskitOperator`.
Args:
parameter_values: Values keyed by parameter name.
Returns:
The bound ``SparsePauliOp``.
"""
params_dict = build_params_dict(self.free_parameters, parameter_values)
if params_dict:
return self.native.assign_parameters(params_dict)
return self.native
def _rebuild(self, ir: PauliIR) -> "QiskitOperator":
"""Wrap a new representation in this operator's type."""
return type(self)(_ir=ir)
def __repr__(self):
return f"QiskitOperator({self.num_qubits} qubits, {len(self.free_parameters)} parameters)"