Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update model classes to use arraylias #294

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
37bf9a9
initial rearranging of generator model
DanPuzzuoli Dec 7, 2023
ff7a5b9
transitioning to updating tests
DanPuzzuoli Dec 8, 2023
1992cbb
validation tests working
DanPuzzuoli Dec 8, 2023
873abd7
deleting old validation test class
DanPuzzuoli Dec 8, 2023
ff09862
updating tests for new operator into frame basis functions
DanPuzzuoli Dec 8, 2023
abdb4d4
generator model tests passing for numpy
DanPuzzuoli Dec 8, 2023
5c7e48c
fixing tests for scipy_sparse
DanPuzzuoli Dec 8, 2023
a4f3d3f
adding jax transformation tests
DanPuzzuoli Dec 8, 2023
632e3ef
deleting old tests for generator model
DanPuzzuoli Dec 8, 2023
8279b5d
deleting old code from generator_model.py
DanPuzzuoli Dec 8, 2023
bbb60dc
linting
DanPuzzuoli Dec 8, 2023
a04625c
updating new is_hermitian
DanPuzzuoli Dec 8, 2023
89a5689
modifying is_hermitian to only work for a 2d array
DanPuzzuoli Dec 8, 2023
0f9d846
adding tests for JAX transformations
DanPuzzuoli Dec 9, 2023
3e2ee26
deleting old code and linting
DanPuzzuoli Dec 9, 2023
49ec65d
initial edit of LindbladModel and validation tests passing
DanPuzzuoli Dec 12, 2023
8a921b8
first round of numerical tests for LindbladModel passing
DanPuzzuoli Dec 13, 2023
885b08c
all tests converted and working
DanPuzzuoli Dec 13, 2023
20eba85
finishing linting
DanPuzzuoli Dec 13, 2023
475994a
Update qiskit_dynamics/models/generator_model.py
DanPuzzuoli Dec 13, 2023
ff7b9fb
Update test/dynamics/models/test_hamiltonian_model.py
DanPuzzuoli Dec 13, 2023
d3e8e00
fixing lazy returns type hint
DanPuzzuoli Dec 13, 2023
7b72122
adding release note
DanPuzzuoli Dec 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
413 changes: 115 additions & 298 deletions qiskit_dynamics/models/generator_model.py

Large diffs are not rendered by default.

198 changes: 78 additions & 120 deletions qiskit_dynamics/models/hamiltonian_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,16 @@

from typing import Union, List, Optional
import numpy as np
from scipy.sparse import csr_matrix, issparse
from scipy.sparse import 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 import DYNAMICS_NUMPY as unp
from qiskit_dynamics import DYNAMICS_NUMPY_ALIAS as numpy_alias
from qiskit_dynamics.arraylias.alias import ArrayLike
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 .generator_model import GeneratorModel
from .rotating_frame import RotatingFrame


Expand All @@ -49,9 +45,11 @@ class HamiltonianModel(GeneratorModel):

.. code-block:: python

hamiltonian = HamiltonianModel(static_operator=static_operator,
operators=operators,
signals=signals)
hamiltonian = HamiltonianModel(
static_operator=static_operator,
operators=operators,
signals=signals
)

This class inherits most functionality from :class:`GeneratorModel`, with the following
modifications:
Expand All @@ -64,12 +62,12 @@ class HamiltonianModel(GeneratorModel):

def __init__(
self,
static_operator: Optional[Array] = None,
operators: Optional[List[Operator]] = None,
static_operator: Optional[ArrayLike] = None,
operators: Optional[ArrayLike] = None,
signals: Optional[Union[SignalList, List[Signal]]] = None,
rotating_frame: Optional[Union[Operator, Array, RotatingFrame]] = None,
rotating_frame: Optional[Union[ArrayLike, RotatingFrame]] = None,
in_frame_basis: bool = False,
evaluation_mode: str = "dense",
array_library: Optional[str] = None,
validate: bool = True,
):
"""Initialize, ensuring that the operators are Hermitian.
Expand All @@ -78,143 +76,103 @@ def __init__(
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.
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.
frame operator is diagonalized.
array_library: Array library for storing the operators in the model. Supported options
are ``'numpy'``, ``'jax'``, ``'jax_sparse'``, and ``'scipy_sparse'``. If ``None``,
the arrays will be handled by general dispatching rules. Call
``help(GeneratorModel.array_library)`` for more details.
validate: If ``True`` check input operators are Hermitian. Note that this is
incompatible with JAX transformations.

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)
# prepare and validate operators
if static_operator is not None:
if validate and not is_hermitian(static_operator):
raise QiskitError("""HamiltonianModel static_operator must be Hermitian.""")
static_operator = -1j * numpy_alias(like=array_library).asarray(static_operator)

if validate:
if (operators is not None) and (not is_hermitian(operators)):
if operators is not None:
if validate and any(not is_hermitian(op) for op in 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.""")

if array_library == "scipy_sparse" or (
array_library is None and "scipy_sparse" in numpy_alias.infer_libs(operators)
):
operators = [-1j * numpy_alias(like=array_library).asarray(op) for op in operators]
else:
operators = -1j * numpy_alias(like=array_library).asarray(operators)

super().__init__(
static_operator=static_operator,
operators=operators,
signals=signals,
rotating_frame=rotating_frame,
in_frame_basis=in_frame_basis,
evaluation_mode=evaluation_mode,
array_library=array_library,
)

@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,
def static_operator(self) -> Union[ArrayLike, None]:
"""The static operator."""
if self._operator_collection.static_operator is None:
return None

if self.in_frame_basis:
return self._operator_collection.static_operator
return 1j * self.rotating_frame.operator_out_of_frame_basis(
self._operator_collection.static_operator
)

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
)

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)

def evaluate_rhs(self, time: float, y: Array) -> Array:
r"""Evaluate ``-1j * H(t) @ y``.
@property
def operators(self) -> Union[ArrayLike, None]:
"""The operators in the model."""
if self._operator_collection.operators is None:
return None

Args:
time: The time to evaluate the model at .
y: Array specifying system state.
if self.in_frame_basis:
ops = self._operator_collection.operators
else:
ops = self.rotating_frame.operator_out_of_frame_basis(
self._operator_collection.operators
)

Returns:
Array defined by :math:`G(t) \times y`.
if isinstance(ops, list):
return [1j * op for op in ops]

Raises:
QiskitError: If model cannot be evaluated.
"""
return -1j * super().evaluate_rhs(time, y)
return 1j * ops


def is_hermitian(
operators: Union[Array, csr_matrix, List[csr_matrix], "BCOO"], tol: Optional[float] = 1e-10
) -> bool:
"""Validate that operators are Hermitian.
def is_hermitian(operator: ArrayLike, tol: Optional[float] = 1e-10) -> bool:
"""Validate that an operator is 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``.
operator: A 2d array representing a single operator.
tol: Tolerance for checking zeros.

Returns:
bool: Whether or not the operators are Hermitian to within tolerance.
bool: Whether or not the operator is 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":
operator = unp.asarray(operator)

if issparse(operator):
return spnorm(operator - operator.conj().transpose()) < tol
elif type(operator).__name__ == "BCOO":
# fall back on array case for BCOO
return is_hermitian(to_array(operators))
return is_hermitian(operator.todense())
elif isinstance(operator, ArrayLike):
adj = None
adj = np.transpose(np.conjugate(operator))
return np.linalg.norm(adj - operator) < tol

raise QiskitError("is_hermitian got an unexpected type.")
Loading