def test_ns_mmq_2site_korzhnev_2005_15n_dq_data_complex128(self): """Test the matrix_exponential() function for higher dimensional data, and compare to matrix_exponential. This uses the data from systemtest Relax_disp.test_korzhnev_2005_15n_dq_data. This test does the matrix exponential in complex128.""" fname = self.data + sep+ "test_korzhnev_2005_15n_dq_data" M0, R20A, R20B, pA, dw, dwH, kex, inv_tcpmg, tcp, num_points, power, back_calc, pB, k_BA, k_AB = self.return_data_mmq_2site(fname) # Extract the total numbers of experiments, number of spins, number of magnetic field strength, number of offsets, maximum number of dispersion point. NS, NM, NO = num_points.shape # Populate the m1 and m2 matrices (only once per function call for speed). m1_mat = rmmq_2site_rankN(R20A=R20A, R20B=R20B, dw=dw, k_AB=k_AB, k_BA=k_BA, tcp=tcp) m2_mat = rmmq_2site_rankN(R20A=R20A, R20B=R20B, dw=-dw, k_AB=k_AB, k_BA=k_BA, tcp=tcp) # The A+/- matrices. A_pos_mat = matrix_exponential(m1_mat) A_neg_mat = matrix_exponential(m2_mat) # Loop over spins. for si in range(NS): # Loop over the spectrometer frequencies. for mi in range(NM): # Loop over offsets: for oi in range(NO): # Extract number of points. num_points_i = num_points[si, mi, oi] # Loop over the time points, back calculating the R2eff values. for i in range(num_points_i): # Test the two different methods. # The A+/- matrices. A_pos_i = A_pos_mat[si, mi, oi, i] A_neg_i = A_neg_mat[si, mi, oi, i] # The lower dimensional matrix exponential. A_pos = np_matrix_exponential(m1_mat[si, mi, oi, i]) A_neg = np_matrix_exponential(m2_mat[si, mi, oi, i]) # Calculate differences diff_A_pos_real = A_pos_i.real - A_pos.real diff_A_pos_real_sum = sum(diff_A_pos_real) diff_A_pos_imag = A_pos_i.imag - A_pos.imag diff_A_pos_imag_sum = sum(diff_A_pos_imag) diff_A_neg_real = A_neg_i.real - A_neg.real diff_A_neg_real_sum = sum(diff_A_neg_real) diff_A_neg_imag = A_neg_i.imag - A_neg.imag diff_A_neg_imag_sum = sum(diff_A_neg_imag) # Test that the sum difference is zero. self.assertAlmostEqual(diff_A_pos_real_sum, 0.0) self.assertAlmostEqual(diff_A_pos_imag_sum, 0.0) self.assertAlmostEqual(diff_A_neg_real_sum, 0.0) self.assertAlmostEqual(diff_A_neg_imag_sum, 0.0)
def test_ns_cpmg_2site_3d_hansen_cpmg_data(self): """Test the matrix_exponential() function for higher dimensional data, and compare to matrix_exponential. This uses the data from systemtest Relax_disp.test_hansen_cpmg_data_to_ns_cpmg_2site_3D.""" fname = self.data + sep + "test_hansen_cpmg_data_to_ns_cpmg_2site_3D" r180x, M0, r10a, r10b, r20a, r20b, pA, dw, dw_orig, kex, inv_tcpmg, tcp, num_points, power, back_calc, pB, k_BA, k_AB = self.return_data_ns_cpmg_2site_3d( fname) # Extract the total numbers of experiments, number of spins, number of magnetic field strength, number of offsets, maximum number of dispersion point. NE, NS, NM, NO, ND = back_calc.shape # The matrix R that contains all the contributions to the evolution, i.e. relaxation, exchange and chemical shift evolution. R_mat = rcpmg_3d_rankN(R1A=r10a, R1B=r10b, R2A=r20a, R2B=r20b, pA=pA, pB=pB, dw=dw, k_AB=k_AB, k_BA=k_BA, tcp=tcp) # This matrix is a propagator that will evolve the magnetization with the matrix R for a delay tcp. Rexpo_mat = matrix_exponential(R_mat) # Loop over the spins for si in range(NS): # Loop over the spectrometer frequencies. for mi in range(NM): # Extract number of points. num_points_si_mi = int(num_points[0, si, mi, 0]) # Loop over the time points, back calculating the R2eff values. for di in range(num_points_si_mi): # Test the two different methods. R_mat_i = R_mat[0, si, mi, 0, di] # The lower dimensional matrix exponential. Rexpo = np_matrix_exponential(R_mat_i) # The higher dimensional matrix exponential. Rexpo_mat_i = Rexpo_mat[0, si, mi, 0, di] diff = Rexpo - Rexpo_mat_i diff_sum = sum(diff) # Test that the sum difference is zero. self.assertAlmostEqual(diff_sum, 0.0)
def test_ns_cpmg_2site_3d_hansen_cpmg_data(self): """Test the matrix_exponential() function for higher dimensional data, and compare to matrix_exponential. This uses the data from systemtest Relax_disp.test_hansen_cpmg_data_to_ns_cpmg_2site_3D.""" fname = self.data + sep+ "test_hansen_cpmg_data_to_ns_cpmg_2site_3D" r180x, M0, r10a, r10b, r20a, r20b, pA, dw, dw_orig, kex, inv_tcpmg, tcp, num_points, power, back_calc, pB, k_BA, k_AB = self.return_data_ns_cpmg_2site_3d(fname) # Extract the total numbers of experiments, number of spins, number of magnetic field strength, number of offsets, maximum number of dispersion point. NE, NS, NM, NO, ND = back_calc.shape # The matrix R that contains all the contributions to the evolution, i.e. relaxation, exchange and chemical shift evolution. R_mat = rcpmg_3d_rankN(R1A=r10a, R1B=r10b, R2A=r20a, R2B=r20b, pA=pA, pB=pB, dw=dw, k_AB=k_AB, k_BA=k_BA, tcp=tcp) # This matrix is a propagator that will evolve the magnetization with the matrix R for a delay tcp. Rexpo_mat = matrix_exponential(R_mat) # Loop over the spins for si in range(NS): # Loop over the spectrometer frequencies. for mi in range(NM): # Extract number of points. num_points_si_mi = int(num_points[0, si, mi, 0]) # Loop over the time points, back calculating the R2eff values. for di in range(num_points_si_mi): # Test the two different methods. R_mat_i = R_mat[0, si, mi, 0, di] # The lower dimensional matrix exponential. Rexpo = np_matrix_exponential(R_mat_i) # The higher dimensional matrix exponential. Rexpo_mat_i = Rexpo_mat[0, si, mi, 0, di] diff = Rexpo - Rexpo_mat_i diff_sum = sum(diff) # Test that the sum difference is zero. self.assertAlmostEqual(diff_sum, 0.0)
def r2eff_ns_mmq_2site_sq_dq_zq(M0=None, F_vector=array([1, 0], float64), R20A=None, R20B=None, pA=None, dw=None, dwH=None, kex=None, inv_tcpmg=None, tcp=None, back_calc=None, num_points=None, power=None): """The 2-site numerical solution to the Bloch-McConnell equation for SQ, ZQ, and DQ data. The notation used here comes from: - Dmitry M. Korzhnev, Philipp Neudecker, Anthony Mittermaier, Vladislav Yu. Orekhov, and Lewis E. Kay (2005). Multiple-site exchange in proteins studied with a suite of six NMR relaxation dispersion experiments: An application to the folding of a Fyn SH3 domain mutant. J. Am. Chem. Soc., 127, 15602-15611. (doi: http://dx.doi.org/10.1021/ja054550e). This function calculates and stores the R2eff values. @keyword M0: This is a vector that contains the initial magnetizations corresponding to the A and B state transverse magnetizations. @type M0: numpy float64, rank-1, 7D array @keyword F_vector: The observable magnitisation vector. This defaults to [1, 0] for X observable magnitisation. @type F_vector: numpy rank-1, 2D float64 array @keyword R20A: The transverse, spin-spin relaxation rate for state A. @type R20A: numpy float array of rank [NS][NM][NO][ND] @keyword R20B: The transverse, spin-spin relaxation rate for state B. @type R20B: numpy float array of rank [NS][NM][NO][ND] @keyword pA: The population of state A. @type pA: float @keyword dw: The combined chemical exchange difference between states A and B in rad/s. It should be set to dwH for 1H SQ data, dw for heteronuclear SQ data, dwH-dw for ZQ data, and dwH+dw for DQ data. @type dw: numpy float array of rank [NS][NM][NO][ND] @keyword dwH: Unused - this is simply to match the r2eff_ns_mmq_2site_mq() function arguments. @type dwH: numpy float array of rank [NS][NM][NO][ND] @keyword kex: The kex parameter value (the exchange rate in rad/s). @type kex: float @keyword inv_tcpmg: The inverse of the total duration of the CPMG element (in inverse seconds). @type inv_tcpmg: numpy float array of rank [NS][NM][NO][ND] @keyword tcp: The tau_CPMG times (1 / 4.nu1). @type tcp: numpy float array of rank [NS][NM][NO][ND] @keyword back_calc: The array for holding the back calculated R2eff values. Each element corresponds to one of the CPMG nu1 frequencies. @type back_calc: numpy float array of rank [NS][NM][NO][ND] @keyword num_points: The number of points on the dispersion curve, equal to the length of the tcp and back_calc arguments. @type num_points: numpy int array of rank [NS][NM][NO] @keyword power: The matrix exponential power array. @type power: numpy int array of rank [NS][NM][NO][ND] """ # Once off parameter conversions. pB = 1.0 - pA k_BA = pA * kex k_AB = pB * kex # This is a vector that contains the initial magnetizations corresponding to the A and B state transverse magnetizations. M0[0] = pA M0[1] = pB # Extract shape of experiment. NS, NM, NO = num_points.shape # Populate the m1 and m2 matrices (only once per function call for speed). m1_mat = rmmq_2site_rankN(R20A=R20A, R20B=R20B, dw=dw, k_AB=k_AB, k_BA=k_BA, tcp=tcp) m2_mat = rmmq_2site_rankN(R20A=R20A, R20B=R20B, dw=-dw, k_AB=k_AB, k_BA=k_BA, tcp=tcp) # The A+/- matrices. A_pos_mat = matrix_exponential(m1_mat, dtype=complex128) A_neg_mat = matrix_exponential(m2_mat, dtype=complex128) # The evolution for one n. evol_block_mat = einsum('...ij, ...jk', A_neg_mat, A_pos_mat) evol_block_mat = einsum('...ij, ...jk', A_neg_mat, evol_block_mat) evol_block_mat = einsum('...ij, ...jk', A_pos_mat, evol_block_mat) # Loop over spins. for si in range(NS): # Loop over the spectrometer frequencies. for mi in range(NM): # Loop over offsets: for oi in range(NO): # Extract number of points. num_points_i = num_points[si, mi, oi] # Loop over the time points, back calculating the R2eff values. for i in range(num_points_i): # Extract data from array. power_i = int(power[si, mi, oi, i]) evol_block_i = evol_block_mat[si, mi, oi, i] # The full evolution. evol = matrix_power(evol_block_i, power_i) # The next lines calculate the R2eff using a two-point approximation, i.e. assuming that the decay is mono-exponential. Mx = dot(F_vector, dot(evol, M0)) Mx = Mx.real if Mx <= 0.0 or isNaN(Mx): back_calc[si, mi, oi, i] = 1e99 else: back_calc[si, mi, oi, i] = -inv_tcpmg[si, mi, oi, i] * log(Mx / pA)
def r2eff_ns_mmq_2site_mq(M0=None, F_vector=array([1, 0], float64), R20A=None, R20B=None, pA=None, dw=None, dwH=None, kex=None, inv_tcpmg=None, tcp=None, back_calc=None, num_points=None, power=None): """The 2-site numerical solution to the Bloch-McConnell equation for MQ data. The notation used here comes from: - Dmitry M. Korzhnev, Philipp Neudecker, Anthony Mittermaier, Vladislav Yu. Orekhov, and Lewis E. Kay (2005). Multiple-site exchange in proteins studied with a suite of six NMR relaxation dispersion experiments: An application to the folding of a Fyn SH3 domain mutant. J. Am. Chem. Soc., 127, 15602-15611. (doi: http://dx.doi.org/10.1021/ja054550e). and: - Dmitry M. Korzhnev, Philipp Neudecker, Anthony Mittermaier, Vladislav Yu. Orekhov, and Lewis E. Kay (2005). Multiple-site exchange in proteins studied with a suite of six NMR relaxation dispersion experiments: An application to the folding of a Fyn SH3 domain mutant. J. Am. Chem. Soc., 127, 15602-15611. (doi: http://dx.doi.org/10.1021/ja054550e). This function calculates and stores the R2eff values. @keyword M0: This is a vector that contains the initial magnetizations corresponding to the A and B state transverse magnetizations. @type M0: numpy float64, rank-1, 7D array @keyword F_vector: The observable magnitisation vector. This defaults to [1, 0] for X observable magnitisation. @type F_vector: numpy rank-1, 2D float64 array @keyword R20A: The transverse, spin-spin relaxation rate for state A. @type R20A: numpy float array of rank [NS][NM][NO][ND] @keyword R20B: The transverse, spin-spin relaxation rate for state B. @type R20B: numpy float array of rank [NS][NM][NO][ND] @keyword pA: The population of state A. @type pA: float @keyword dw: The chemical exchange difference between states A and B in rad/s. @type dw: numpy float array of rank [NS][NM][NO][ND] @keyword dwH: The proton chemical exchange difference between states A and B in rad/s. @type dwH: numpy float array of rank [NS][NM][NO][ND] @keyword kex: The kex parameter value (the exchange rate in rad/s). @type kex: float @keyword inv_tcpmg: The inverse of the total duration of the CPMG element (in inverse seconds). @type inv_tcpmg: numpy float array of rank [NS][NM][NO][ND] @keyword tcp: The tau_CPMG times (1 / 4.nu1). @type tcp: numpy float array of rank [NS][NM][NO][ND] @keyword back_calc: The array for holding the back calculated R2eff values. Each element corresponds to one of the CPMG nu1 frequencies. @type back_calc: numpy float array of rank [NS][NM][NO][ND] @keyword num_points: The number of points on the dispersion curve, equal to the length of the tcp and back_calc arguments. @type num_points: numpy int array of rank [NS][NM][NO] @keyword power: The matrix exponential power array. @type power: numpy int array of rank [NS][NM][NO][ND] """ # Once off parameter conversions. pB = 1.0 - pA k_BA = pA * kex k_AB = pB * kex # This is a vector that contains the initial magnetizations corresponding to the A and B state transverse magnetizations. M0[0] = pA M0[1] = pB # Extract shape of experiment. NS, NM, NO = num_points.shape # Populate the m1 and m2 matrices (only once per function call for speed). # D+ matrix component. m1_mat = rmmq_2site_rankN(R20A=R20A, R20B=R20B, dw=-dw - dwH, k_AB=k_AB, k_BA=k_BA, tcp=tcp) # Z- matrix component. m2_mat = rmmq_2site_rankN(R20A=R20A, R20B=R20B, dw=dw - dwH, k_AB=k_AB, k_BA=k_BA, tcp=tcp) # The M1 and M2 matrices. # Equivalent to D+. M1_mat = matrix_exponential(m1_mat, dtype=complex128) # Equivalent to Z-. M2_mat = matrix_exponential(m2_mat, dtype=complex128) # The complex conjugates M1* and M2* # Equivalent to D+*. M1_star_mat = conj(M1_mat) # Equivalent to Z-*. M2_star_mat = conj(M2_mat) # Repetitive dot products (minimised for speed). M1_M2_mat = einsum('...ij, ...jk', M1_mat, M2_mat) M2_M1_mat = einsum('...ij, ...jk', M2_mat, M1_mat) M1_M2_M2_M1_mat = einsum('...ij, ...jk', M1_M2_mat, M2_M1_mat) M2_M1_M1_M2_mat = einsum('...ij, ...jk', M2_M1_mat, M1_M2_mat) M1_M2_star_mat = einsum('...ij, ...jk', M1_star_mat, M2_star_mat) M2_M1_star_mat = einsum('...ij, ...jk', M2_star_mat, M1_star_mat) M1_M2_M2_M1_star_mat = einsum('...ij, ...jk', M1_M2_star_mat, M2_M1_star_mat) M2_M1_M1_M2_star_mat = einsum('...ij, ...jk', M2_M1_star_mat, M1_M2_star_mat) # Loop over spins. for si in range(NS): # Loop over the spectrometer frequencies. for mi in range(NM): # Loop over offsets: for oi in range(NO): num_points_i = num_points[si, mi, oi] # Loop over the time points, back calculating the R2eff values. for i in range(num_points_i): # Extract data from array. power_i = int(power[si, mi, oi, i]) M1_M2_i = M1_M2_mat[si, mi, oi, i] M1_M2_star_i = M1_M2_star_mat[si, mi, oi, i] M2_M1_i = M2_M1_mat[si, mi, oi, i] M2_M1_star_i = M2_M1_star_mat[si, mi, oi, i] M1_M2_M2_M1_i = M1_M2_M2_M1_mat[si, mi, oi, i] M2_M1_M1_M2_star_i = M2_M1_M1_M2_star_mat[si, mi, oi, i] M2_M1_M1_M2_i = M2_M1_M1_M2_mat[si, mi, oi, i] M1_M2_M2_M1_star_i = M1_M2_M2_M1_star_mat[si, mi, oi, i] # Special case of 1 CPMG block - the power is zero. if power_i == 1: # M1.M2. A = M1_M2_i # M1*.M2*. B = M1_M2_star_i # M2.M1. C = M2_M1_i # M2*.M1*. D = M2_M1_star_i # Matrices for even number of CPMG blocks. elif power_i % 2 == 0: # The power factor (only calculate once). fact = int(floor(power_i / 2)) # (M1.M2.M2.M1)^(n/2). A = matrix_power(M1_M2_M2_M1_i, fact) # (M2*.M1*.M1*.M2*)^(n/2). B = matrix_power(M2_M1_M1_M2_star_i, fact) # (M2.M1.M1.M2)^(n/2). C = matrix_power(M2_M1_M1_M2_i, fact) # (M1*.M2*.M2*.M1*)^(n/2). D = matrix_power(M1_M2_M2_M1_star_i, fact) # Matrices for odd number of CPMG blocks. else: # The power factor (only calculate once). fact = int(floor((power_i - 1) / 2)) # (M1.M2.M2.M1)^((n-1)/2).M1.M2. A = matrix_power(M1_M2_M2_M1_i, fact) A = dot(A, M1_M2_i) # (M1*.M2*.M2*.M1*)^((n-1)/2).M1*.M2*. B = matrix_power(M1_M2_M2_M1_star_i, fact) B = dot(B, M1_M2_star_i) # (M2.M1.M1.M2)^((n-1)/2).M2.M1. C = matrix_power(M2_M1_M1_M2_i, fact) C = dot(C, M2_M1_i) # (M2*.M1*.M1*.M2*)^((n-1)/2).M2*.M1*. D = matrix_power(M2_M1_M1_M2_star_i, fact) D = dot(D, M2_M1_star_i) # The next lines calculate the R2eff using a two-point approximation, i.e. assuming that the decay is mono-exponential. A_B = dot(A, B) C_D = dot(C, D) Mx = dot(dot(F_vector, (A_B + C_D)), M0) Mx = Mx.real / 2.0 if Mx <= 0.0 or isNaN(Mx): back_calc[si, mi, oi, i] = 1e99 else: back_calc[si, mi, oi, i] = -inv_tcpmg[si, mi, oi, i] * log(Mx / pA)
def ns_r1rho_2site(M0=None, M0_T=None, r1rho_prime=None, omega=None, offset=None, r1=0.0, pA=None, dw=None, kex=None, spin_lock_fields=None, relax_time=None, inv_relax_time=None, back_calc=None): """The 2-site numerical solution to the Bloch-McConnell equation for R1rho data. This function calculates and stores the R1rho values. @keyword M0: This is a vector that contains the initial magnetizations corresponding to the A and B state transverse magnetizations. @type M0: numpy float array of rank [NE][NS][NM][NO][ND][6][1] @keyword M0_T: This is a vector that contains the initial magnetizations corresponding to the A and B state transverse magnetizations, where the outer two axis has been swapped for efficient dot operations. @type M0_T: numpy float array of rank [NE][NS][NM][NO][ND][1][6] @keyword r1rho_prime: The R1rho_prime parameter value (R1rho with no exchange). @type r1rho_prime: numpy float array of rank [NE][NS][NM][NO][ND] @keyword omega: The chemical shift for the spin in rad/s. @type omega: numpy float array of rank [NE][NS][NM][NO][ND] @keyword offset: The spin-lock offsets for the data. @type offset: numpy float array of rank [NE][NS][NM][NO][ND] @keyword r1: The R1 relaxation rate. @type r1: numpy float array of rank [NE][NS][NM][NO][ND] @keyword pA: The population of state A. @type pA: float @keyword dw: The chemical exchange difference between states A and B in rad/s. @type dw: numpy float array of rank [NE][NS][NM][NO][ND] @keyword kex: The kex parameter value (the exchange rate in rad/s). @type kex: float @keyword spin_lock_fields: The R1rho spin-lock field strengths (in rad.s^-1). @type spin_lock_fields: numpy float array of rank [NE][NS][NM][NO][ND] @keyword relax_time: The total relaxation time period for each spin-lock field strength (in seconds). @type relax_time: numpy float array of rank [NE][NS][NM][NO][ND] @keyword inv_relax_time: The inverse of the relaxation time period for each spin-lock field strength (in inverse seconds). This is used for faster calculations. @type inv_relax_time: numpy float array of rank [NE][NS][NM][NO][ND] @keyword back_calc: The array for holding the back calculated R2eff values. Each element corresponds to one of the CPMG nu1 frequencies. @type back_calc: numpy float array of rank [NE][NS][NM][NO][ND] """ # Once off parameter conversions. pB = 1.0 - pA k_BA = pA * kex k_AB = pB * kex # The matrix that contains all the contributions to the evolution, i.e. relaxation, exchange and chemical shift evolution. R_mat = rr1rho_3d_2site_rankN(R1=r1, r1rho_prime=r1rho_prime, dw=dw, omega=omega, offset=offset, w1=spin_lock_fields, k_AB=k_AB, k_BA=k_BA, relax_time=relax_time) # This matrix is a propagator that will evolve the magnetization with the matrix R. Rexpo_mat = matrix_exponential(R_mat) # Magnetization evolution. Rexpo_M0_mat = einsum('...ij, ...jk', Rexpo_mat, M0) # Magnetization evolution, which include all dimensions. MA_mat = einsum('...ij, ...jk', M0_T, Rexpo_M0_mat)[:, :, :, :, :, 0, 0] # Insert safe checks. if min(MA_mat) < 0.0: mask_min_MA_mat = masked_less(MA_mat, 0.0) # Fill with high values. MA_mat[mask_min_MA_mat.mask] = 1e100 # Do back calculation. back_calc[:] = -inv_relax_time * log(MA_mat) # Catch errors, taking a sum over array is the fastest way to check for # +/- inf (infinity) and nan (not a number). if not isfinite(sum(back_calc)): # Replaces nan, inf, etc. with fill value. fix_invalid(back_calc, copy=False, fill_value=1e100)
def r2eff_ns_cpmg_2site_star(M0=None, r20a=None, r20b=None, pA=None, dw=None, dw_orig=None, kex=None, inv_tcpmg=None, tcp=None, back_calc=None, num_points=None, power=None): """The 2-site numerical solution to the Bloch-McConnell equation using complex conjugate matrices. This function calculates and stores the R2eff values. @keyword M0: This is a vector that contains the initial magnetizations corresponding to the A and B state transverse magnetizations. @type M0: numpy float64, rank-1, 2D array @keyword r20a: The R2 value for state A in the absence of exchange. @type r20a: numpy float array of rank [NE][NS][NM][NO][ND] @keyword r20b: The R2 value for state B in the absence of exchange. @type r20b: numpy float array of rank [NE][NS][NM][NO][ND] @keyword pA: The population of state A. @type pA: float @keyword dw: The chemical exchange difference between states A and B in rad/s. @type dw: numpy float array of rank [NE][NS][NM][NO][ND] @keyword dw_orig: The chemical exchange difference between states A and B in ppm. This is only for faster checking of zero value, which result in no exchange. @type dw_orig: numpy float array of rank-1 @keyword kex: The kex parameter value (the exchange rate in rad/s). @type kex: float @keyword inv_tcpmg: The inverse of the total duration of the CPMG element (in inverse seconds). @type inv_tcpmg: numpy float array of rank [NE][NS][NM][NO][ND] @keyword tcp: The tau_CPMG times (1 / 4.nu1). @type tcp: numpy float array of rank [NE][NS][NM][NO][ND] @keyword back_calc: The array for holding the back calculated R2eff values. Each element corresponds to one of the CPMG nu1 frequencies. @type back_calc: numpy float array of rank [NE][NS][NM][NO][ND] @keyword num_points: The number of points on the dispersion curve, equal to the length of the tcp and back_calc arguments. @type num_points: numpy int array of rank [NE][NS][NM][NO] @keyword power: The matrix exponential power array. @type power: numpy int array of rank [NE][NS][NM][NO][ND] """ # Flag to tell if values should be replaced if math function is violated. t_dw_zero = False # Catch parameter values that will result in no exchange, returning flat R2eff = R20 lines (when kex = 0.0, k_AB = 0.0). if pA == 1.0 or kex == 0.0: back_calc[:] = r20a return # Test if dw is zero. Create a mask for the affected spins to replace these with R20 at the end of the calculationWait for replacement, since this is spin specific. if min(fabs(dw_orig)) == 0.0: t_dw_zero = True mask_dw_zero = masked_where(dw == 0.0, dw) # Once off parameter conversions. pB = 1.0 - pA k_BA = pA * kex k_AB = pB * kex # This is a vector that contains the initial magnetizations corresponding to the A and B state transverse magnetizations. M0[0] = pA M0[1] = pB # Extract the total numbers of experiments, number of spins, number of magnetic field strength, number of offsets, maximum number of dispersion point. NE, NS, NM, NO, ND = back_calc.shape # The matrix R that contains all the contributions to the evolution, i.e. relaxation, exchange and chemical shift evolution. R_mat, cR2_mat, Rr_mat, Rex_mat, RCS_mat = rcpmg_star_rankN(R2A=r20a, R2B=r20b, dw=dw, k_AB=k_AB, k_BA=k_BA, tcp=tcp) # The the essential evolution matrix. # This matrix is a propagator that will evolve the magnetization with the matrix R for a delay tcp. eR_mat = matrix_exponential(R_mat) ecR2_mat = matrix_exponential(cR2_mat) # Preform the matrix. # This is the propagator for an element of [delay tcp; 180 deg pulse; 2 times delay tcp; 180 deg pulse; delay tau], i.e. for 2 times tau-180-tau. prop_2_mat = evolution_matrix_mat = einsum('...ij, ...jk', eR_mat, ecR2_mat) prop_2_mat = evolution_matrix_mat = einsum('...ij, ...jk', prop_2_mat, eR_mat) # Loop over the spins for si in range(NS): # Loop over the spectrometer frequencies. for mi in range(NM): # Extract the values from the higher dimensional arrays. num_points_si_mi = int(num_points[0, si, mi, 0]) # Loop over the time points, back calculating the R2eff values. for di in range(num_points_si_mi): # Extract the values from the higher dimensional arrays. power_si_mi_di = int(power[0, si, mi, 0, di]) # This is the propagator for an element of [delay tcp; 180 deg pulse; 2 times delay tcp; 180 deg pulse; delay tau], i.e. for 2 times tau-180-tau. prop_2_i = prop_2_mat[0, si, mi, 0, di] # Now create the total propagator that will evolve the magnetization under the CPMG train, i.e. it applies the above tau-180-tau-tau-180-tau so many times as required for the CPMG frequency under consideration. prop_total = matrix_power(prop_2_i, power_si_mi_di) # Now we apply the above propagator to the initial magnetization vector - resulting in the magnetization that remains after the full CPMG pulse train. It is called M of t (t is the time after the CPMG train). Moft = dot(prop_total, M0) # The next lines calculate the R2eff using a two-point approximation, i.e. assuming that the decay is mono-exponential. Mx = Moft[0].real / M0[0] if Mx <= 0.0 or isNaN(Mx): back_calc[0, si, mi, 0, di] = 1e99 else: back_calc[0, si, mi, 0, di]= -inv_tcpmg[0, si, mi, 0, di] * log(Mx) # Replace data in array. # If dw is zero. if t_dw_zero: back_calc[mask_dw_zero.mask] = r20a[mask_dw_zero.mask] # Catch errors, taking a sum over array is the fastest way to check for # +/- inf (infinity) and nan (not a number). if not isfinite(sum(back_calc)): # Replaces nan, inf, etc. with fill value. fix_invalid(back_calc, copy=False, fill_value=1e100)
def ns_r1rho_3site(M0=None, M0_T=None, r1rho_prime=None, omega=None, offset=None, r1=0.0, pA=None, pB=None, dw_AB=None, dw_BC=None, kex_AB=None, kex_BC=None, kex_AC=None, spin_lock_fields=None, relax_time=None, inv_relax_time=None, back_calc=None, num_points=None): """The 3-site numerical solution to the Bloch-McConnell equation for R1rho data. This function calculates and stores the R1rho values. @keyword M0: This is a vector that contains the initial magnetizations corresponding to the A and B state transverse magnetizations. @type M0: numpy float array of rank [NE][NS][NM][NO][ND][9][1] @keyword M0_T: This is a vector that contains the initial magnetizations corresponding to the A and B state transverse magnetizations, where the outer two axis has been swapped for efficient dot operations. @keyword r1rho_prime: The R1rho_prime parameter value (R1rho with no exchange). @type r1rho_prime: numpy float array of rank [NE][NS][NM][NO][ND][1][9] @keyword omega: The chemical shift for the spin in rad/s. @type omega: numpy float array of rank [NS][NM][NO][ND] @keyword offset: The spin-lock offsets for the data. @type offset: numpy float array of rank [NS][NM][NO][ND] @keyword r1: The R1 relaxation rate. @type r1: numpy float array of rank [NS][NM][NO][ND] @keyword pA: The population of state A. @type pA: float @keyword pB: The population of state B. @type pB: float @keyword dw_AB: The chemical exchange difference between states A and B in rad/s. @type dw_AB: numpy float array of rank [NS][NM][NO][ND] @keyword dw_BC: The chemical exchange difference between states B and C in rad/s. @type dw_BC: numpy float array of rank [NS][NM][NO][ND] @keyword kex_AB: The exchange rate between sites A and B for 3-site exchange with kex_AB = k_AB + k_BA (rad.s^-1) @type kex_AB: float @keyword kex_BC: The exchange rate between sites A and C for 3-site exchange with kex_AC = k_AC + k_CA (rad.s^-1) @type kex_BC: float @keyword kex_AC: The exchange rate between sites B and C for 3-site exchange with kex_BC = k_BC + k_CB (rad.s^-1) @type kex_AC: float @keyword spin_lock_fields: The R1rho spin-lock field strengths (in rad.s^-1). @type spin_lock_fields: numpy float array of rank [NS][NM][NO][ND] @keyword relax_time: The total relaxation time period for each spin-lock field strength (in seconds). @type relax_time: numpy float array of rank [NS][NM][NO][ND] @keyword inv_relax_time: The inverse of the relaxation time period for each spin-lock field strength (in inverse seconds). This is used for faster calculations. @type inv_relax_time: numpy float array of rank [NS][NM][NO][ND] @keyword back_calc: The array for holding the back calculated R2eff values. Each element corresponds to one of the CPMG nu1 frequencies. @type back_calc: numpy float array of rank [NS][NM][NO][ND] @keyword num_points: The number of points on the dispersion curve, equal to the length of the tcp and back_calc arguments. @type num_points: numpy int array of rank [NS][NM][NO] """ # Once off parameter conversions. dw_AC = dw_AB + dw_BC pC = 1.0 - pA - pB pA_pB = pA + pB pA_pC = pA + pC pB_pC = pB + pC k_BA = pA * kex_AB / pA_pB k_AB = pB * kex_AB / pA_pB k_CB = pB * kex_BC / pB_pC k_BC = pC * kex_BC / pB_pC k_CA = pA * kex_AC / pA_pC k_AC = pC * kex_AC / pA_pC # Extract shape of experiment. NE, NS, NM, NO = num_points.shape # The matrix that contains all the contributions to the evolution, i.e. relaxation, exchange and chemical shift evolution. R_mat = rr1rho_3d_3site_rankN(R1=r1, r1rho_prime=r1rho_prime, omega=omega, offset=offset, dw_AB=dw_AB, dw_AC=dw_AC, w1=spin_lock_fields, k_AB=k_AB, k_BA=k_BA, k_BC=k_BC, k_CB=k_CB, k_AC=k_AC, k_CA=k_CA, relax_time=relax_time) # This matrix is a propagator that will evolve the magnetization with the matrix R. Rexpo_mat = matrix_exponential(R_mat) # Magnetization evolution. Rexpo_M0_mat = einsum('...ij, ...jk', Rexpo_mat, M0) # Magnetization evolution, which include all dimensions. MA_mat = einsum('...ij, ...jk', M0_T, Rexpo_M0_mat)[:, :, :, :, :, 0, 0] # Insert safe checks. if min(MA_mat) < 0.0: mask_min_MA_mat = masked_less(MA_mat, 0.0) # Fill with high values. MA_mat[mask_min_MA_mat.mask] = 1e100 # Do back calculation. back_calc[:] = -inv_relax_time * log(MA_mat) # Catch errors, taking a sum over array is the fastest way to check for # +/- inf (infinity) and nan (not a number). if not isfinite(sum(back_calc)): # Replaces nan, inf, etc. with fill value. fix_invalid(back_calc, copy=False, fill_value=1e100)
def r2eff_ns_mmq_2site_mq(M0=None, F_vector=array([1, 0], float64), R20A=None, R20B=None, pA=None, dw=None, dwH=None, kex=None, inv_tcpmg=None, tcp=None, back_calc=None, num_points=None, power=None): """The 2-site numerical solution to the Bloch-McConnell equation for MQ data. The notation used here comes from: - Dmitry M. Korzhnev, Philipp Neudecker, Anthony Mittermaier, Vladislav Yu. Orekhov, and Lewis E. Kay (2005). Multiple-site exchange in proteins studied with a suite of six NMR relaxation dispersion experiments: An application to the folding of a Fyn SH3 domain mutant. J. Am. Chem. Soc., 127, 15602-15611. (doi: http://dx.doi.org/10.1021/ja054550e). and: - Dmitry M. Korzhnev, Philipp Neudecker, Anthony Mittermaier, Vladislav Yu. Orekhov, and Lewis E. Kay (2005). Multiple-site exchange in proteins studied with a suite of six NMR relaxation dispersion experiments: An application to the folding of a Fyn SH3 domain mutant. J. Am. Chem. Soc., 127, 15602-15611. (doi: http://dx.doi.org/10.1021/ja054550e). This function calculates and stores the R2eff values. @keyword M0: This is a vector that contains the initial magnetizations corresponding to the A and B state transverse magnetizations. @type M0: numpy float64, rank-1, 7D array @keyword F_vector: The observable magnitisation vector. This defaults to [1, 0] for X observable magnitisation. @type F_vector: numpy rank-1, 2D float64 array @keyword R20A: The transverse, spin-spin relaxation rate for state A. @type R20A: numpy float array of rank [NS][NM][NO][ND] @keyword R20B: The transverse, spin-spin relaxation rate for state B. @type R20B: numpy float array of rank [NS][NM][NO][ND] @keyword pA: The population of state A. @type pA: float @keyword dw: The chemical exchange difference between states A and B in rad/s. @type dw: numpy float array of rank [NS][NM][NO][ND] @keyword dwH: The proton chemical exchange difference between states A and B in rad/s. @type dwH: numpy float array of rank [NS][NM][NO][ND] @keyword kex: The kex parameter value (the exchange rate in rad/s). @type kex: float @keyword inv_tcpmg: The inverse of the total duration of the CPMG element (in inverse seconds). @type inv_tcpmg: numpy float array of rank [NS][NM][NO][ND] @keyword tcp: The tau_CPMG times (1 / 4.nu1). @type tcp: numpy float array of rank [NS][NM][NO][ND] @keyword back_calc: The array for holding the back calculated R2eff values. Each element corresponds to one of the CPMG nu1 frequencies. @type back_calc: numpy float array of rank [NS][NM][NO][ND] @keyword num_points: The number of points on the dispersion curve, equal to the length of the tcp and back_calc arguments. @type num_points: numpy int array of rank [NS][NM][NO] @keyword power: The matrix exponential power array. @type power: numpy int array of rank [NS][NM][NO][ND] """ # Once off parameter conversions. pB = 1.0 - pA k_BA = pA * kex k_AB = pB * kex # This is a vector that contains the initial magnetizations corresponding to the A and B state transverse magnetizations. M0[0] = pA M0[1] = pB # Extract shape of experiment. NS, NM, NO = num_points.shape # Populate the m1 and m2 matrices (only once per function call for speed). # D+ matrix component. m1_mat = rmmq_2site_rankN(R20A=R20A, R20B=R20B, dw=-dw - dwH, k_AB=k_AB, k_BA=k_BA, tcp=tcp) # Z- matrix component. m2_mat = rmmq_2site_rankN(R20A=R20A, R20B=R20B, dw=dw - dwH, k_AB=k_AB, k_BA=k_BA, tcp=tcp) # The M1 and M2 matrices. # Equivalent to D+. M1_mat = matrix_exponential(m1_mat, dtype=complex128) # Equivalent to Z-. M2_mat = matrix_exponential(m2_mat, dtype=complex128) # The complex conjugates M1* and M2* # Equivalent to D+*. M1_star_mat = conj(M1_mat) # Equivalent to Z-*. M2_star_mat = conj(M2_mat) # Repetitive dot products (minimised for speed). M1_M2_mat = einsum('...ij, ...jk', M1_mat, M2_mat) M2_M1_mat = einsum('...ij, ...jk', M2_mat, M1_mat) M1_M2_M2_M1_mat = einsum('...ij, ...jk', M1_M2_mat, M2_M1_mat) M2_M1_M1_M2_mat = einsum('...ij, ...jk', M2_M1_mat, M1_M2_mat) M1_M2_star_mat = einsum('...ij, ...jk', M1_star_mat, M2_star_mat) M2_M1_star_mat = einsum('...ij, ...jk', M2_star_mat, M1_star_mat) M1_M2_M2_M1_star_mat = einsum('...ij, ...jk', M1_M2_star_mat, M2_M1_star_mat) M2_M1_M1_M2_star_mat = einsum('...ij, ...jk', M2_M1_star_mat, M1_M2_star_mat) # Loop over spins. for si in range(NS): # Loop over the spectrometer frequencies. for mi in range(NM): # Loop over offsets: for oi in range(NO): num_points_i = num_points[si, mi, oi] # Loop over the time points, back calculating the R2eff values. for i in range(num_points_i): # Extract data from array. power_i = int(power[si, mi, oi, i]) M1_M2_i = M1_M2_mat[si, mi, oi, i] M1_M2_star_i = M1_M2_star_mat[si, mi, oi, i] M2_M1_i = M2_M1_mat[si, mi, oi, i] M2_M1_star_i = M2_M1_star_mat[si, mi, oi, i] M1_M2_M2_M1_i = M1_M2_M2_M1_mat[si, mi, oi, i] M2_M1_M1_M2_star_i = M2_M1_M1_M2_star_mat[si, mi, oi, i] M2_M1_M1_M2_i = M2_M1_M1_M2_mat[si, mi, oi, i] M1_M2_M2_M1_star_i = M1_M2_M2_M1_star_mat[si, mi, oi, i] # Special case of 1 CPMG block - the power is zero. if power_i == 1: # M1.M2. A = M1_M2_i # M1*.M2*. B = M1_M2_star_i # M2.M1. C = M2_M1_i # M2*.M1*. D = M2_M1_star_i # Matrices for even number of CPMG blocks. elif power_i % 2 == 0: # The power factor (only calculate once). fact = int(floor(power_i / 2)) # (M1.M2.M2.M1)^(n/2). A = matrix_power(M1_M2_M2_M1_i, fact) # (M2*.M1*.M1*.M2*)^(n/2). B = matrix_power(M2_M1_M1_M2_star_i, fact) # (M2.M1.M1.M2)^(n/2). C = matrix_power(M2_M1_M1_M2_i, fact) # (M1*.M2*.M2*.M1*)^(n/2). D = matrix_power(M1_M2_M2_M1_star_i, fact) # Matrices for odd number of CPMG blocks. else: # The power factor (only calculate once). fact = int(floor((power_i - 1) / 2)) # (M1.M2.M2.M1)^((n-1)/2).M1.M2. A = matrix_power(M1_M2_M2_M1_i, fact) A = dot(A, M1_M2_i) # (M1*.M2*.M2*.M1*)^((n-1)/2).M1*.M2*. B = matrix_power(M1_M2_M2_M1_star_i, fact) B = dot(B, M1_M2_star_i) # (M2.M1.M1.M2)^((n-1)/2).M2.M1. C = matrix_power(M2_M1_M1_M2_i, fact) C = dot(C, M2_M1_i) # (M2*.M1*.M1*.M2*)^((n-1)/2).M2*.M1*. D = matrix_power(M2_M1_M1_M2_star_i, fact) D = dot(D, M2_M1_star_i) # The next lines calculate the R2eff using a two-point approximation, i.e. assuming that the decay is mono-exponential. A_B = dot(A, B) C_D = dot(C, D) Mx = dot(dot(F_vector, (A_B + C_D)), M0) Mx = Mx.real / 2.0 if Mx <= 0.0 or isNaN(Mx): back_calc[si, mi, oi, i] = 1e99 else: back_calc[si, mi, oi, i]= -inv_tcpmg[si, mi, oi, i] * log(Mx / pA)
def r2eff_ns_cpmg_2site_3D(r180x=None, M0=None, M0_T=None, r10a=0.0, r10b=0.0, r20a=None, r20b=None, pA=None, dw=None, dw_orig=None, kex=None, inv_tcpmg=None, tcp=None, back_calc=None, num_points=None, power=None): """The 2-site numerical solution to the Bloch-McConnell equation. This function calculates and stores the R2eff values. @keyword r180x: The X-axis pi-pulse propagator. @type r180x: numpy float64, rank-2, 7D array @keyword M0: This is a vector that contains the initial magnetizations corresponding to the A and B state transverse magnetizations. @type M0: numpy float array of rank [NE][NS][NM][NO][ND][7][1] @keyword M0_T: This is a vector that contains the initial magnetizations corresponding to the A and B state transverse magnetizations, where the outer two axis has been swapped for efficient dot operations. @type M0_T: numpy float array of rank [NE][NS][NM][NO][ND][1][7] @keyword r10a: The R1 value for state A. @type r10a: float @keyword r10b: The R1 value for state B. @type r10b: float @keyword r20a: The R2 value for state A in the absence of exchange. @type r20a: numpy float array of rank [NE][NS][NM][NO][ND] @keyword r20b: The R2 value for state B in the absence of exchange. @type r20b: numpy float array of rank [NE][NS][NM][NO][ND] @keyword pA: The population of state A. @type pA: float @keyword dw: The chemical exchange difference between states A and B in rad/s. @type dw: numpy float array of rank [NE][NS][NM][NO][ND] @keyword dw_orig: The chemical exchange difference between states A and B in ppm. This is only for faster checking of zero value, which result in no exchange. @type dw_orig: numpy float array of rank-1 @keyword kex: The kex parameter value (the exchange rate in rad/s). @type kex: float @keyword inv_tcpmg: The inverse of the total duration of the CPMG element (in inverse seconds). @type inv_tcpmg: numpy float array of rank [NE][NS][NM][NO][ND] @keyword tcp: The tau_CPMG times (1 / 4.nu1). @type tcp: numpy float array of rank [NE][NS][NM][NO][ND] @keyword back_calc: The array for holding the back calculated R2eff values. Each element corresponds to one of the CPMG nu1 frequencies. @type back_calc: numpy float array of rank [NE][NS][NM][NO][ND] @keyword num_points: The number of points on the dispersion curve, equal to the length of the tcp and back_calc arguments. @type num_points: numpy int array of rank [NE][NS][NM][NO] @keyword power: The matrix exponential power array. @type power: numpy int array of rank [NE][NS][NM][NO][ND] """ # Flag to tell if values should be replaced if math function is violated. t_dw_zero = False # Catch parameter values that will result in no exchange, returning flat R2eff = R20 lines (when kex = 0.0, k_AB = 0.0). if pA == 1.0 or kex == 0.0: back_calc[:] = r20a return # Test if dw is zero. Create a mask for the affected spins to replace these with R20 at the end of the calculationWait for replacement, since this is spin specific. if min(fabs(dw_orig)) == 0.0: t_dw_zero = True mask_dw_zero = masked_where(dw == 0.0, dw) # Once off parameter conversions. pB = 1.0 - pA k_BA = pA * kex k_AB = pB * kex # This is a vector that contains the initial magnetizations corresponding to the A and B state transverse magnetizations. M0_T[:, :, :, :, :, 0, 1] = pA M0_T[:, :, :, :, :, 0, 4] = pB M0[:, :, :, :, :, 1, 0] = pA M0[:, :, :, :, :, 4, 0] = pB # Extract the total numbers of experiments, number of spins, number of magnetic field strength, number of offsets, maximum number of dispersion point. NE, NS, NM, NO, ND = back_calc.shape # The matrix R that contains all the contributions to the evolution, i.e. relaxation, exchange and chemical shift evolution. R_mat = rcpmg_3d_rankN(R1A=r10a, R1B=r10b, R2A=r20a, R2B=r20b, pA=pA, pB=pB, dw=dw, k_AB=k_AB, k_BA=k_BA, tcp=tcp) # This matrix is a propagator that will evolve the magnetization with the matrix R for a delay tcp. Rexpo_mat = matrix_exponential(R_mat) # The the essential evolution matrix. # This is a dot product of the outer [7][7] matrix of the Rexpo_mat and r180x matrixes, which # have the shape [NE][NS][NM][NO][ND][7][7] and [7][7]. # This can be achieved by using numpy einsum, and where ellipsis notation will use the last axis. evolution_matrix_mat = einsum('...ij,...jk', Rexpo_mat, r180x) evolution_matrix_mat = einsum('...ij,...jk', evolution_matrix_mat, Rexpo_mat) evolution_matrix_mat = einsum('...ij,...jk', evolution_matrix_mat, evolution_matrix_mat) # Roll axis around. evolution_matrix_T_mat = rollaxis(evolution_matrix_mat, 6, 5) # Preform the initial magnetisation. evolution_matrix_T_M0_mat = einsum('...ij,...jk', M0_T, evolution_matrix_T_mat) # Loop over the spins for si in range(NS): # Loop over the spectrometer frequencies. for mi in range(NM): # Extract number of points. num_points_si_mi = int(num_points[0, si, mi, 0]) # Loop over the time points, back calculating the R2eff values. for di in range(num_points_si_mi): # Extract the values from the higher dimensional arrays. inv_tcpmg_si_mi_di = inv_tcpmg[0, si, mi, 0, di] power_si_mi_di = int(power[0, si, mi, 0, di]) r20a_si_mi_di = r20a[0, si, mi, 0, di] # Initial magnetisation. Mint_T_i = evolution_matrix_T_M0_mat[0, si, mi, 0, di] # This matrix is a propagator that will evolve the magnetization with the matrix R for a delay tcp. evolution_matrix_T_i = evolution_matrix_T_mat[0, si, mi, 0, di] # Get which power to raise the matrix to. l = int(power_si_mi_di-1) # Raise the square evolution matrix to the power l. evolution_matrix_T_power_i = matrix_power(evolution_matrix_T_i, l) # Evolve the magnetisation. Mint_T_i = dot(Mint_T_i, evolution_matrix_T_power_i) # The next lines calculate the R2eff using a two-point approximation, i.e. assuming that the decay is mono-exponential. Mx = Mint_T_i[0][1] / pA if Mx <= 0.0 or isNaN(Mx): back_calc[0, si, mi, 0, di] = r20a_si_mi_di else: back_calc[0, si, mi, 0, di] = - inv_tcpmg_si_mi_di * log(Mx) # Replace data in array. # If dw is zero. if t_dw_zero: back_calc[mask_dw_zero.mask] = r20a[mask_dw_zero.mask] # Catch errors, taking a sum over array is the fastest way to check for # +/- inf (infinity) and nan (not a number). if not isfinite(sum(back_calc)): # Replaces nan, inf, etc. with fill value. fix_invalid(back_calc, copy=False, fill_value=1e100)
def test_ns_mmq_2site_korzhnev_2005_15n_dq_data_complex128(self): """Test the matrix_exponential() function for higher dimensional data, and compare to matrix_exponential. This uses the data from systemtest Relax_disp.test_korzhnev_2005_15n_dq_data. This test does the matrix exponential in complex128.""" fname = self.data + sep + "test_korzhnev_2005_15n_dq_data" M0, R20A, R20B, pA, dw, dwH, kex, inv_tcpmg, tcp, num_points, power, back_calc, pB, k_BA, k_AB = self.return_data_mmq_2site( fname) # Extract the total numbers of experiments, number of spins, number of magnetic field strength, number of offsets, maximum number of dispersion point. NS, NM, NO = num_points.shape # Populate the m1 and m2 matrices (only once per function call for speed). m1_mat = rmmq_2site_rankN(R20A=R20A, R20B=R20B, dw=dw, k_AB=k_AB, k_BA=k_BA, tcp=tcp) m2_mat = rmmq_2site_rankN(R20A=R20A, R20B=R20B, dw=-dw, k_AB=k_AB, k_BA=k_BA, tcp=tcp) # The A+/- matrices. A_pos_mat = matrix_exponential(m1_mat) A_neg_mat = matrix_exponential(m2_mat) # Loop over spins. for si in range(NS): # Loop over the spectrometer frequencies. for mi in range(NM): # Loop over offsets: for oi in range(NO): # Extract number of points. num_points_i = num_points[si, mi, oi] # Loop over the time points, back calculating the R2eff values. for i in range(num_points_i): # Test the two different methods. # The A+/- matrices. A_pos_i = A_pos_mat[si, mi, oi, i] A_neg_i = A_neg_mat[si, mi, oi, i] # The lower dimensional matrix exponential. A_pos = np_matrix_exponential(m1_mat[si, mi, oi, i]) A_neg = np_matrix_exponential(m2_mat[si, mi, oi, i]) # Calculate differences diff_A_pos_real = A_pos_i.real - A_pos.real diff_A_pos_real_sum = sum(diff_A_pos_real) diff_A_pos_imag = A_pos_i.imag - A_pos.imag diff_A_pos_imag_sum = sum(diff_A_pos_imag) diff_A_neg_real = A_neg_i.real - A_neg.real diff_A_neg_real_sum = sum(diff_A_neg_real) diff_A_neg_imag = A_neg_i.imag - A_neg.imag diff_A_neg_imag_sum = sum(diff_A_neg_imag) # Test that the sum difference is zero. self.assertAlmostEqual(diff_A_pos_real_sum, 0.0) self.assertAlmostEqual(diff_A_pos_imag_sum, 0.0) self.assertAlmostEqual(diff_A_neg_real_sum, 0.0) self.assertAlmostEqual(diff_A_neg_imag_sum, 0.0)
def r2eff_ns_cpmg_2site_3D(r180x=None, M0=None, M0_T=None, r10a=0.0, r10b=0.0, r20a=None, r20b=None, pA=None, dw=None, dw_orig=None, kex=None, inv_tcpmg=None, tcp=None, back_calc=None, num_points=None, power=None): """The 2-site numerical solution to the Bloch-McConnell equation. This function calculates and stores the R2eff values. @keyword r180x: The X-axis pi-pulse propagator. @type r180x: numpy float64, rank-2, 7D array @keyword M0: This is a vector that contains the initial magnetizations corresponding to the A and B state transverse magnetizations. @type M0: numpy float array of rank [NE][NS][NM][NO][ND][7][1] @keyword M0_T: This is a vector that contains the initial magnetizations corresponding to the A and B state transverse magnetizations, where the outer two axis has been swapped for efficient dot operations. @type M0_T: numpy float array of rank [NE][NS][NM][NO][ND][1][7] @keyword r10a: The R1 value for state A. @type r10a: float @keyword r10b: The R1 value for state B. @type r10b: float @keyword r20a: The R2 value for state A in the absence of exchange. @type r20a: numpy float array of rank [NE][NS][NM][NO][ND] @keyword r20b: The R2 value for state B in the absence of exchange. @type r20b: numpy float array of rank [NE][NS][NM][NO][ND] @keyword pA: The population of state A. @type pA: float @keyword dw: The chemical exchange difference between states A and B in rad/s. @type dw: numpy float array of rank [NE][NS][NM][NO][ND] @keyword dw_orig: The chemical exchange difference between states A and B in ppm. This is only for faster checking of zero value, which result in no exchange. @type dw_orig: numpy float array of rank-1 @keyword kex: The kex parameter value (the exchange rate in rad/s). @type kex: float @keyword inv_tcpmg: The inverse of the total duration of the CPMG element (in inverse seconds). @type inv_tcpmg: numpy float array of rank [NE][NS][NM][NO][ND] @keyword tcp: The tau_CPMG times (1 / 4.nu1). @type tcp: numpy float array of rank [NE][NS][NM][NO][ND] @keyword back_calc: The array for holding the back calculated R2eff values. Each element corresponds to one of the CPMG nu1 frequencies. @type back_calc: numpy float array of rank [NE][NS][NM][NO][ND] @keyword num_points: The number of points on the dispersion curve, equal to the length of the tcp and back_calc arguments. @type num_points: numpy int array of rank [NE][NS][NM][NO] @keyword power: The matrix exponential power array. @type power: numpy int array of rank [NE][NS][NM][NO][ND] """ # Flag to tell if values should be replaced if math function is violated. t_dw_zero = False # Catch parameter values that will result in no exchange, returning flat R2eff = R20 lines (when kex = 0.0, k_AB = 0.0). if pA == 1.0 or kex == 0.0: back_calc[:] = r20a return # Test if dw is zero. Create a mask for the affected spins to replace these with R20 at the end of the calculationWait for replacement, since this is spin specific. if min(fabs(dw_orig)) == 0.0: t_dw_zero = True mask_dw_zero = masked_where(dw == 0.0, dw) # Once off parameter conversions. pB = 1.0 - pA k_BA = pA * kex k_AB = pB * kex # This is a vector that contains the initial magnetizations corresponding to the A and B state transverse magnetizations. M0_T[:, :, :, :, :, 0, 1] = pA M0_T[:, :, :, :, :, 0, 4] = pB M0[:, :, :, :, :, 1, 0] = pA M0[:, :, :, :, :, 4, 0] = pB # Extract the total numbers of experiments, number of spins, number of magnetic field strength, number of offsets, maximum number of dispersion point. NE, NS, NM, NO, ND = back_calc.shape # The matrix R that contains all the contributions to the evolution, i.e. relaxation, exchange and chemical shift evolution. R_mat = rcpmg_3d_rankN(R1A=r10a, R1B=r10b, R2A=r20a, R2B=r20b, pA=pA, pB=pB, dw=dw, k_AB=k_AB, k_BA=k_BA, tcp=tcp) # This matrix is a propagator that will evolve the magnetization with the matrix R for a delay tcp. Rexpo_mat = matrix_exponential(R_mat) # The the essential evolution matrix. # This is a dot product of the outer [7][7] matrix of the Rexpo_mat and r180x matrixes, which # have the shape [NE][NS][NM][NO][ND][7][7] and [7][7]. # This can be achieved by using numpy einsum, and where ellipsis notation will use the last axis. evolution_matrix_mat = einsum('...ij,...jk', Rexpo_mat, r180x) evolution_matrix_mat = einsum('...ij,...jk', evolution_matrix_mat, Rexpo_mat) evolution_matrix_mat = einsum('...ij,...jk', evolution_matrix_mat, evolution_matrix_mat) # Roll axis around. evolution_matrix_T_mat = rollaxis(evolution_matrix_mat, 6, 5) # Preform the initial magnetisation. evolution_matrix_T_M0_mat = einsum('...ij,...jk', M0_T, evolution_matrix_T_mat) # Loop over the spins for si in range(NS): # Loop over the spectrometer frequencies. for mi in range(NM): # Extract number of points. num_points_si_mi = int(num_points[0, si, mi, 0]) # Loop over the time points, back calculating the R2eff values. for di in range(num_points_si_mi): # Extract the values from the higher dimensional arrays. inv_tcpmg_si_mi_di = inv_tcpmg[0, si, mi, 0, di] power_si_mi_di = int(power[0, si, mi, 0, di]) r20a_si_mi_di = r20a[0, si, mi, 0, di] # Initial magnetisation. Mint_T_i = evolution_matrix_T_M0_mat[0, si, mi, 0, di] # This matrix is a propagator that will evolve the magnetization with the matrix R for a delay tcp. evolution_matrix_T_i = evolution_matrix_T_mat[0, si, mi, 0, di] # Get which power to raise the matrix to. l = int(power_si_mi_di - 1) # Raise the square evolution matrix to the power l. evolution_matrix_T_power_i = matrix_power( evolution_matrix_T_i, l) # Evolve the magnetisation. Mint_T_i = dot(Mint_T_i, evolution_matrix_T_power_i) # The next lines calculate the R2eff using a two-point approximation, i.e. assuming that the decay is mono-exponential. Mx = Mint_T_i[0][1] / pA if Mx <= 0.0 or isNaN(Mx): back_calc[0, si, mi, 0, di] = r20a_si_mi_di else: back_calc[0, si, mi, 0, di] = -inv_tcpmg_si_mi_di * log(Mx) # Replace data in array. # If dw is zero. if t_dw_zero: back_calc[mask_dw_zero.mask] = r20a[mask_dw_zero.mask] # Catch errors, taking a sum over array is the fastest way to check for # +/- inf (infinity) and nan (not a number). if not isfinite(sum(back_calc)): # Replaces nan, inf, etc. with fill value. fix_invalid(back_calc, copy=False, fill_value=1e100)