Ejemplo n.º 1
0
    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)
Ejemplo n.º 2
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)
Ejemplo n.º 3
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)
Ejemplo n.º 4
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)
Ejemplo n.º 5
0
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)
Ejemplo n.º 6
0
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)
Ejemplo n.º 7
0
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)
Ejemplo n.º 8
0
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)
Ejemplo n.º 9
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)
Ejemplo n.º 10
0
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)
Ejemplo n.º 11
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)
Ejemplo n.º 12
0
    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)
Ejemplo n.º 13
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)
Ejemplo n.º 14
0
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)