Source code for povm_toolbox.quantum_info.multi_qubit_dual
# (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.
"""MultiQubitDual."""
from __future__ import annotations
import sys
if sys.version_info < (3, 12):
from typing_extensions import override
else:
from typing import override # pragma: no cover
import numpy as np
from qiskit.quantum_info import Operator
from povm_toolbox.utilities import double_ket_to_matrix
from .base import BaseDual, BaseFrame
from .multi_qubit_frame import MultiQubitFrame
[docs]
class MultiQubitDual(MultiQubitFrame, BaseDual):
"""Class that collects all information that any Dual over multiple qubits should specify.
This is a representation of a dual frame. Its elements are specified as a list of
:class:`~qiskit.quantum_info.Operator`.
"""
[docs]
@override
def is_dual_to(self, frame: BaseFrame) -> bool:
if isinstance(frame, MultiQubitFrame):
return np.allclose(frame @ np.conj(self).T, np.eye(self.dimension**2), atol=1e-6)
raise NotImplementedError
[docs]
@override
@classmethod
def build_dual_from_frame(
cls, frame: BaseFrame, alphas: tuple[float, ...] | None = None
) -> MultiQubitDual:
if isinstance(frame, MultiQubitFrame):
# Set default values for alphas if none is provided.
if alphas is None:
alphas = tuple(np.real(np.trace(frame_op.data)) for frame_op in frame.operators)
# Check that the number of alpha-parameters match the number of operators
# forming the ``frame``.
elif len(alphas) != frame.num_operators:
raise ValueError(
f"The number of alpha-parameters should be equal to the number of"
f" operators in the frame ({frame.num_operators}). Here, {len(alphas)}"
" parameters were provided."
)
# Set the weighting matrix according to the alpha-parameters
diag_trace = np.diag([1.0 / alpha for alpha in alphas])
# Compute the weighed frame super-operator.
superop = frame @ diag_trace @ np.conj(frame).T
# Solve the linear system to find the dual operators.
dual_operators_array = np.linalg.solve(
superop,
frame @ diag_trace,
)
# Convert dual operators from double-ket to operator representation.
dual_operators = [Operator(double_ket_to_matrix(op)) for op in dual_operators_array.T]
return cls(dual_operators)
# We could build a ``MultiQubitDual`` instance (i.e. joint dual frame) that
# is a dual frame to a ``ProductFrame``, but we have not implemented this yet.
raise NotImplementedError(f"Not implemented for {type(frame)}")