quspin.tools.misc.get_matvec_function
- quspin.tools.misc.get_matvec_function(array)[source]
Determines automatically the matrix vector product routine for an array based on its type.
A highly specialized omp-parallelized application of a matrix-vector (matvec) product depends on the array type (csr, csc, dia, other [e.g., dense]). This function determines automatically which matvec routine is most appropriate for a given array type.
- Parameters:
- arrayarray_like object (e.g. numpy.ndarray, scipy.sparse.csr_matrix, scipy.sparse.csc_matrix, scipy.sparse.dia_matrix)
Array-like object to determine the most appropriate omp-parallelized matvec function for.
- Returns:
- python function object
A python function to perform the matrix-vector product. For appropriate use, see tools.misc.matvec().
Notes
for QuSpin builds which support OpenMP, this function will be multithreaded.
see function tools.misc.matvec() which shows how to use the python function returned by get_matvec_function().
the difference between tools.misc.matvec() and the python function returned by get_matvec_function is that tools.misc.matvec() determines the correct matrix-vector product type every time it is called, while get_matvec_function allows to circumvent this extra overhead and gain some speed.
Examples
The example shows how to use the get_matvec_function() (line 43) and the matvec() function (lines 47-81) in a user-defined ODE which solves the Lindblad equation for a single qubit (see also Example 17).
1# 2from quspin.operators import hamiltonian, commutator, anti_commutator 3from quspin.basis import spin_basis_1d # Hilbert space spin basis_1d 4from quspin.tools.evolution import evolve 5from quspin.tools.misc import get_matvec_function 6import numpy as np 7from six import iteritems # loop over elements of dictionary 8import matplotlib.pyplot as plt # plotting library 9 10# 11###### model parameters 12# 13L = 1 # one qubit 14delta = 1.0 # detuning 15Omega_0 = np.sqrt(2) # bare Rabi frequency 16Omega_Rabi = np.sqrt(Omega_0**2 + delta**2) # Rabi frequency 17gamma = 0.5 * np.sqrt(3) # decay rate 18# 19##### create Hamiltonian to evolve unitarily 20# basis 21basis = spin_basis_1d( 22 L, pauli=-1 23) # uses convention "+" = [[0,1],[0,0]] (mind the spin factor 1/2) 24# site-coupling lists 25hx_list = [[Omega_0, i] for i in range(L)] 26hz_list = [[delta, i] for i in range(L)] 27# static opstr list 28static_H = [["x", hx_list], ["z", hz_list]] 29# hamiltonian 30H = hamiltonian(static_H, [], basis=basis, dtype=np.float64) 31print("Hamiltonian:\n", H.toarray()) 32# 33##### create Lindbladian 34# site-coupling lists 35L_list = [[1.0, i] for i in range(L)] 36# static opstr list 37static_L = [["+", L_list]] 38# Lindblad operator 39L = hamiltonian(static_L, [], basis=basis, dtype=np.complex128, check_herm=False) 40print("Lindblad operator:\n", L.toarray()) 41# pre-compute operators for efficiency 42L_dagger = L.getH() 43L_daggerL = L_dagger * L 44# 45#### determine the corresponding matvec routines #### 46# 47# different matvec functions are required since we use both matrices and their transposed, cf. Lindblad_EOM_v3 48matvec_csr = get_matvec_function(H.static) # csr matvec function 49matvec_csc = get_matvec_function(H.static.T) # csc matvec function 50 51 52# 53# fast function (not as memory efficient) 54def Lindblad_EOM(time, rho, rho_out, rho_aux): 55 """ 56 This function solves the complex-valued time-dependent GPE: 57 $$ \dot\rho(t) = -i[H,\rho(t)] + 2\gamma\left( L\rho L^\dagger - \frac{1}{2}\{L^\dagger L, \rho \} \right) $$ 58 """ 59 rho = rho.reshape((H.Ns, H.Ns)) # reshape vector from ODE solver input 60 ### Hamiltonian part 61 # commutator term (unitary 62 # rho_out = H.static.dot(rho)) 63 matvec_csr(H.static, rho, out=rho_out, a=+1.0, overwrite_out=True) 64 # rho_out -= (H.static.T.dot(rho.T)).T // RHS~rho.dot(H) 65 matvec_csc(H.static.T, rho.T, out=rho_out.T, a=-1.0, overwrite_out=False) 66 # 67 for func, Hd in iteritems(H._dynamic): 68 ft = func(time) 69 # rho_out += ft*Hd.dot(rho) 70 matvec_csr(Hd, rho, out=rho_out, a=+ft, overwrite_out=False) 71 # rho_out -= ft*(Hd.T.dot(rho.T)).T 72 matvec_csc(Hd.T, rho.T, out=rho_out.T, a=-ft, overwrite_out=False) 73 # multiply by -i 74 rho_out *= -1.0j 75 # 76 ### Lindbladian part (static only) 77 ## 1st Lindblad term (nonunitary): 2\gamma * L*rho*L^\dagger 78 # rho_aux = 2\gamma*L.dot(rho) 79 matvec_csr(L.static, rho, out=rho_aux, a=+2.0 * gamma, overwrite_out=True) 80 # rho_out += (L.static.conj().dot(rho_aux.T)).T // RHS ~ rho_aux.dot(L_dagger) 81 matvec_csr(L.static.conj(), rho_aux.T, out=rho_out.T, a=+1.0, overwrite_out=False) 82 # 83 ## anticommutator (2nd Lindblad) term (nonunitary): -\gamma \{L^\dagger * L, \eho} 84 # rho_out += gamma*L_daggerL.static.dot(rho) 85 matvec_csr(L_daggerL.static, rho, out=rho_out, a=-gamma, overwrite_out=False) 86 # rho_out += gamma*(L_daggerL.static.T.dot(rho.T)).T // RHS~rho.dot(L_daggerL) 87 matvec_csc(L_daggerL.static.T, rho.T, out=rho_out.T, a=-gamma, overwrite_out=False) 88 # 89 return rho_out.ravel() # ODE solver accepts vectors only 90 91 92# 93# define auxiliary arguments 94EOM_args = ( 95 np.zeros( 96 (H.Ns, H.Ns), dtype=np.complex128, order="C" 97 ), # auxiliary variable rho_out 98 np.zeros((H.Ns, H.Ns), dtype=np.complex128, order="C"), 99) # auxiliary variable rho_aux 100# 101##### time-evolve state according to Lindlad equation 102# define real time vector 103t_max = 6.0 104time = np.linspace(0.0, t_max, 101) 105# define initial state 106rho0 = np.array([[0.5, 0.5j], [-0.5j, 0.5]], dtype=np.complex128) 107# 108# evolution 109rho_t = evolve( 110 rho0, 111 time[0], 112 time, 113 Lindblad_EOM, 114 f_params=EOM_args, 115 iterate=True, 116 atol=1e-12, 117 rtol=1e-12, 118) 119# 120# compute state evolution 121population_down = np.zeros(time.shape, dtype=np.float64) 122for i, rho_flattened in enumerate(rho_t): 123 rho = rho_flattened.reshape(H.Ns, H.Ns) 124 population_down[i] = rho_flattened[1, 1].real 125 print( 126 "time={0:.2f}, population of down state = {1:0.8f}".format( 127 time[i], population_down[i] 128 ) 129 )