Source code for spinguin.core.propagation

"""
This module is responsible for calculating time propagators.
"""

# Imports
import time
import numpy as np
import scipy.sparse as sp
import warnings
from spinguin.core.la import expm
from spinguin.core.superoperators import sop_from_string
from spinguin.core.hide_prints import HidePrints

[docs] def sop_propagator(L: np.ndarray | sp.csc_array, t: float, zero_value: float=1e-18, density_threshold: float=0.5) -> sp.csc_array | np.ndarray: """ Constructs the time propagator exp(L*t). Parameters ---------- L : ndarray or csc_array Liouvillian superoperator, L = -iH - R + K. t : float Time step of the simulation in seconds. zero_value : float, default=1e-18 Calculating the propagator involves a matrix exponential, which is calculated using the scaling and squaring method together with Taylor series. This threshold is used to estimate the convergence of the Taylor series and to eliminate small values during the squaring step. density_threshold : float, default=0.5 Sparse matrix is returned if the density is less than this threshold. Otherwise dense matrix is returned. Returns ------- expm_Lt : csc_array or ndarray Time propagator exp(L*t). """ print("Constructing propagator...") time_start = time.time() # Compute the matrix exponential expm_Lt = expm(L * t, zero_value) # Calculate the density of the propagator density = expm_Lt.nnz / (expm_Lt.shape[0] ** 2) print(f"Propagator density: {density:.4f}") # Convert to NumPy array if density exceeds the threshold if density > density_threshold: print("Density exceeds threshold. Converting to NumPy array.") expm_Lt = expm_Lt.toarray() print(f'Propagator constructed in {time.time() - time_start:.4f} seconds.') print() return expm_Lt
[docs] def propagator_to_rotframe( sop_P: np.ndarray | sp.csc_array, sop_H0: np.ndarray | sp.csc_array, t: float, zero_value: float=1e-18 ) -> np.ndarray | sp.csc_array: """ Transforms the time propagator to the rotating frame. Parameters ---------- sop_P : ndarray or csc_array Time propagator. sop_H0 : ndarray or csc_array Hamiltonian superoperator representing the interaction used to define the rotating frame transformation. t : float Time step of the simulation in seconds. zero_value : float, default=1e-18 Calculating the rotating frame transformation involves a matrix exponential, which is calculated using the scaling and squaring method together with Taylor series. This threshold is used to estimate the convergence of the Taylor series and to eliminate small values during the squaring step. Returns ------- sop_P : ndarray or csc_array The time propagator transformed into the rotating frame. """ print("Applying rotating frame transformation...") time_start = time.time() # Acquire matrix exponential from the Hamiltonian with HidePrints(): expm_H0t = expm(1j * sop_H0 * t, zero_value) # Convert the time propagator to rotating frame sop_P = expm_H0t @ sop_P print("Rotating frame transformation applied in " f"{time.time() - time_start:.4f} seconds.") print() return sop_P
[docs] def sop_pulse(basis: np.ndarray, spins: np.ndarray, operator: str, angle: float, sparse: bool=True, zero_value: float=1e-18) -> np.ndarray | sp.csc_array: """ Generates a superoperator corresponding to the pulse described by the given operator and angle. Parameters ---------- basis : ndarray A 2-dimensional array containing the basis set that contains sequences of integers describing the Kronecker products of irreducible spherical tensors. spins : ndarray A 1-dimensional array containing the spin quantum numbers of each spin. operator : str Defines the pulse to be generated. The operator string must follow the rules below: - Cartesian and ladder operators: `I(component,index)` or `I(component)`. Examples: - `I(x,4)` --> Creates x-operator for spin at index 4. - `I(x)`--> Creates x-operator for all spins. - Spherical tensor operators: `T(l,q,index)` or `T(l,q)`. Examples: - `T(1,-1,3)` --> \ Creates operator with `l=1`, `q=-1` for spin at index 3. - `T(1, -1)` --> \ Creates operator with `l=1`, `q=-1` for all spins. - Product operators have `*` in between the single-spin operators: `I(z,0) * I(z,1)` - Sums of operators have `+` in between the operators: `I(x,0) + I(x,1)` - Unit operators are ignored in the input. Interpretation of these two is identical: `E * I(z,1)`, `I(z,1)` Special case: An empty `operator` string is considered as unit operator. Whitespace will be ignored in the input. NOTE: Indexing starts from 0! angle : float Pulse angle in degrees. sparse : bool, default=True Specifies whether to construct the pulse superoperator as sparse or dense array. zero_value : float, default=1e-18 Calculating the pulse superoperator involves a matrix exponential, which is calculated using the scaling and squaring method together with Taylor series. This threshold is used to estimate the convergence of the Taylor series and to eliminate small values during the squaring step. Returns ------- pul : ndarray or csc_array Superoperator corresponding to the applied pulse. """ time_start = time.time() print("Creating a pulse superoperator...") # Show a warning if pulse is generated using a product operator if '*' in operator: warnings.warn("Applying a pulse using a product operator does not have " "a well-defined angle.") # Generate the operator op = sop_from_string(operator, basis, spins, side="comm", sparse=sparse) # Convert the angle to radians angle = angle / 180 * np.pi # Construct the pulse propagator with HidePrints(): pul = expm(-1j * angle * op, zero_value) print(f'Pulse constructed in {time.time() - time_start:.4f} seconds.\n') return pul