Source code for qiskit_dynamics.models.hamiltonian_model

# This code is part of Qiskit.
#
# (C) Copyright IBM 2020.
#
# 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.
# pylint: disable=invalid-name

"""
Hamiltonian models module.
"""

from typing import Union, List, Optional
import numpy as np
from scipy.sparse import csr_matrix, issparse
from scipy.sparse.linalg import norm as spnorm

from qiskit import QiskitError
from qiskit.quantum_info.operators import Operator
from qiskit_dynamics.array import Array
from qiskit_dynamics.signals import Signal, SignalList
from qiskit_dynamics.type_utils import to_numeric_matrix_type, to_array
from .generator_model import (
    GeneratorModel,
    transfer_static_operator_between_frames,
    transfer_operators_between_frames,
    construct_operator_collection,
)
from .rotating_frame import RotatingFrame


[docs] class HamiltonianModel(GeneratorModel): r"""A model of a Hamiltonian for the Schrodinger equation. This class represents a Hamiltonian as a time-dependent decomposition the form: .. math:: H(t) = H_d + \sum_j s_j(t) H_j, where :math:`H_j` are Hermitian operators, :math:`H_d` is the static component, and the :math:`s_j(t)` are either :class:`~qiskit_dynamics.signals.Signal` objects or numerical constants. Constructing a :class:`~qiskit_dynamics.models.HamiltonianModel` requires specifying the above decomposition, e.g.: .. code-block:: python hamiltonian = HamiltonianModel(static_operator=static_operator, operators=operators, signals=signals) This class inherits most functionality from :class:`GeneratorModel`, with the following modifications: * The operators :math:`H_d` and :math:`H_j` are assumed and verified to be Hermitian. * Rotating frames are dealt with assuming the structure of the Schrodinger equation. I.e. Evaluating the Hamiltonian :math:`H(t)` in a frame :math:`F = -iH_0`, evaluates the expression :math:`e^{-tF}H(t)e^{tF} - H_0`. """ def __init__( self, static_operator: Optional[Array] = None, operators: Optional[List[Operator]] = None, signals: Optional[Union[SignalList, List[Signal]]] = None, rotating_frame: Optional[Union[Operator, Array, RotatingFrame]] = None, in_frame_basis: bool = False, evaluation_mode: str = "dense", validate: bool = True, ): """Initialize, ensuring that the operators are Hermitian. Args: static_operator: Time-independent term in the Hamiltonian. operators: List of Operator objects. signals: List of coefficients :math:`s_i(t)`. Not required at instantiation, but necessary for evaluation of the model. rotating_frame: Rotating frame operator. If specified with a 1d array, it is interpreted as the diagonal of a diagonal matrix. Assumed to store the antihermitian matrix F = -iH. in_frame_basis: Whether to represent the model in the basis in which the rotating frame operator is diagonalized. evaluation_mode: Evaluation mode to use. Supported options are ``'dense'`` and ``'sparse'``. Call ``help(HamiltonianModel.evaluation_mode)`` for more details. validate: If True check input operators are Hermitian. Raises: QiskitError: if operators are not Hermitian """ # verify operators are Hermitian, and if so instantiate operators = to_numeric_matrix_type(operators) static_operator = to_numeric_matrix_type(static_operator) if validate: if (operators is not None) and (not is_hermitian(operators)): raise QiskitError("""HamiltonianModel operators must be Hermitian.""") if (static_operator is not None) and (not is_hermitian(static_operator)): raise QiskitError("""HamiltonianModel static_operator must be Hermitian.""") super().__init__( static_operator=static_operator, operators=operators, signals=signals, rotating_frame=rotating_frame, in_frame_basis=in_frame_basis, evaluation_mode=evaluation_mode, ) @property def rotating_frame(self) -> RotatingFrame: """The rotating frame. This property can be set with a :class:`RotatingFrame` instance, or any valid constructor argument to this class. It can either be Hermitian or anti-Hermitian, and in either case only the anti-Hermitian version :math:`F=-iH` will be stored. Setting this property updates the internal operator list to use the new frame. """ return super().rotating_frame @rotating_frame.setter def rotating_frame(self, rotating_frame: Union[Operator, Array, RotatingFrame]) -> Array: new_frame = RotatingFrame(rotating_frame) # convert static operator to new frame setup static_op = self._get_static_operator(in_frame_basis=True) if static_op is not None: static_op = -1j * static_op new_static_operator = transfer_static_operator_between_frames( static_op, new_frame=new_frame, old_frame=self.rotating_frame, ) if new_static_operator is not None: new_static_operator = 1j * new_static_operator # convert operators to new frame set up new_operators = transfer_operators_between_frames( self._get_operators(in_frame_basis=True), new_frame=new_frame, old_frame=self.rotating_frame, ) self._rotating_frame = new_frame self._operator_collection = construct_operator_collection( self.evaluation_mode, new_static_operator, new_operators )
[docs] def evaluate(self, time: float) -> Array: """Evaluate the model in array format as a matrix, independent of state. Args: time: The time to evaluate the model at. Returns: Array: The evaluated model as an anti-Hermitian matrix. Raises: QiskitError: If model cannot be evaluated. """ return -1j * super().evaluate(time)
[docs] def evaluate_rhs(self, time: float, y: Array) -> Array: r"""Evaluate ``-1j * H(t) @ y``. Args: time: The time to evaluate the model at . y: Array specifying system state. Returns: Array defined by :math:`G(t) \times y`. Raises: QiskitError: If model cannot be evaluated. """ return -1j * super().evaluate_rhs(time, y)
def is_hermitian( operators: Union[Array, csr_matrix, List[csr_matrix], "BCOO"], tol: Optional[float] = 1e-10 ) -> bool: """Validate that operators are Hermitian. Args: operators: Either a 2d array representing a single operator, a 3d array representing a list of operators, a ``csr_matrix``, or a list of ``csr_matrix``. tol: Tolerance for checking zeros. Returns: bool: Whether or not the operators are Hermitian to within tolerance. Raises: QiskitError: If an unexpeted type is received. """ if isinstance(operators, (np.ndarray, Array)): adj = None if operators.ndim == 2: adj = np.transpose(np.conjugate(operators)) elif operators.ndim == 3: adj = np.transpose(np.conjugate(operators), (0, 2, 1)) return np.linalg.norm(adj - operators) < tol elif issparse(operators): return spnorm(operators - operators.conj().transpose()) < tol elif isinstance(operators, list) and issparse(operators[0]): return all(spnorm(op - op.conj().transpose()) < tol for op in operators) elif type(operators).__name__ == "BCOO": # fall back on array case for BCOO return is_hermitian(to_array(operators)) raise QiskitError("is_hermitian got an unexpected type.")