def test_matrix_exponential(self):
        """Test the matrix exponential function matrix_exponential() with real matrices."""

        # The 3D, rank-2 matrices.
        R1 = array([[1, 4, 5],
                    [-4, 2, 6],
                    [-5, -6, 3]], float64)
        R2 = array([[0, 1, 0],
                    [0, 0, 0],
                    [0, 0, 0]], float64)

        # The real matrix exponentials.
        eR1 = array([[-1.242955024379687, -3.178944439554645,  6.804083368075889],
                     [-6.545353831891249, -2.604941866769356,  1.228233941393001],
                     [ 0.975355249080821, -7.711099455690256, -3.318642157729292]], float64)
        eR2 = array([[ 1.,  0.,  0.],
                     [ 0.,  1.,  0.],
                     [ 0.,  0.,  1.]], float64)

        # The maths.
        eR1_test = matrix_exponential(R1)
        eR2_test = matrix_exponential(R2)

        # Printouts.
        print("Real matrix:\n%s" % eR1)
        print("Calculated matrix:\n%s" % eR1_test)

        # Checks.
        for i in range(3):
            for j in range(3):
                self.assertAlmostEqual(eR1_test[i, j], eR1[i, j])
                self.assertAlmostEqual(eR2_test[i, j], eR2[i, j])
Example #2
0
    def test_matrix_exponential(self):
        """Test the matrix exponential function matrix_exponential() with real matrices."""

        # The 3D, rank-2 matrices.
        R1 = array([[1, 4, 5], [-4, 2, 6], [-5, -6, 3]], float64)
        R2 = array([[0, 1, 0], [0, 0, 0], [0, 0, 0]], float64)

        # The real matrix exponentials.
        eR1 = array(
            [[-1.242955024379687, -3.178944439554645, 6.804083368075889],
             [-6.545353831891249, -2.604941866769356, 1.228233941393001],
             [0.975355249080821, -7.711099455690256, -3.318642157729292]],
            float64)
        eR2 = array([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]], float64)

        # The maths.
        eR1_test = matrix_exponential(R1)
        eR2_test = matrix_exponential(R2)

        # Printouts.
        print("Real matrix:\n%s" % eR1)
        print("Calculated matrix:\n%s" % eR1_test)

        # Checks.
        for i in range(3):
            for j in range(3):
                self.assertAlmostEqual(eR1_test[i, j], eR1[i, j])
                self.assertAlmostEqual(eR2_test[i, j], eR2[i, j])
Example #3
0
    def test_matrix_exponential2(self):
        """Test the matrix exponential function matrix_exponential() with complex matrices."""

        # The 3D, rank-2 matrix.
        R1 = array([[
            -0.024156250059605 + 0.j, 0.021093750372529 + 0.j
        ], [0.021093750372529 + 0.j, -0.024156250059605 - 0.587233662605286j]],
                   complex64)

        # The real matrix exponentials.
        eR1 = array([[
            0.976344227790833 - 4.17836126871407e-05j,
            0.0194285903126001 - 0.00587434694170952j
        ],
                     [
                         0.0194285865873098 - 0.00587435066699982j,
                         0.812806785106659 - 0.540918707847595j
                     ]], complex64)

        # The maths.
        eR1_test = matrix_exponential(R1)

        # Printouts.
        print(
            "Real matrix:\n[%20.15g %20.15gj, %20.15g %20.15gj],\n[%20.15g %20.15gj, %20.15g %20.15gj]\n"
            % (eR1[0, 0].real, eR1[0, 0].imag, eR1[0, 1].real, eR1[0, 1].imag,
               eR1[1, 0].real, eR1[1, 0].imag, eR1[1, 1].real, eR1[1, 1].imag))
        print(
            "Calculated matrix:\n[%20.15g %20.15gj, %20.15g %20.15gj],\n[%20.15g %20.15gj, %20.15g %20.15gj]\n"
            % (eR1_test[0, 0].real, eR1_test[0, 0].imag, eR1_test[0, 1].real,
               eR1_test[0, 1].imag, eR1_test[1, 0].real, eR1_test[1, 0].imag,
               eR1_test[1, 1].real, eR1_test[1, 1].imag))

        # Checks.
        for i in range(2):
            for j in range(2):
                self.assertAlmostEqual(eR1_test[i, j].real, eR1[i, j].real, 5)
                self.assertAlmostEqual(eR1_test[i, j].imag, eR1[i, j].imag, 5)
    def test_matrix_exponential2(self):
        """Test the matrix exponential function matrix_exponential() with complex matrices."""

        # The 3D, rank-2 matrix.
        R1 = array([[-0.024156250059605+0.j, 0.021093750372529+0.j],
                    [ 0.021093750372529+0.j, -0.024156250059605-0.587233662605286j]], complex64)

        # The real matrix exponentials.
        eR1 = array([[ 0.976344227790833 -4.17836126871407e-05j,  0.0194285903126001 -0.00587434694170952j],
                     [ 0.0194285865873098 -0.00587435066699982j,  0.812806785106659  -0.540918707847595j]], complex64)

        # The maths.
        eR1_test = matrix_exponential(R1)

        # Printouts.
        print("Real matrix:\n[%20.15g %20.15gj, %20.15g %20.15gj],\n[%20.15g %20.15gj, %20.15g %20.15gj]\n" % (eR1[0, 0].real, eR1[0, 0].imag, eR1[0, 1].real, eR1[0, 1].imag, eR1[1, 0].real, eR1[1, 0].imag, eR1[1, 1].real, eR1[1, 1].imag))
        print("Calculated matrix:\n[%20.15g %20.15gj, %20.15g %20.15gj],\n[%20.15g %20.15gj, %20.15g %20.15gj]\n" % (eR1_test[0, 0].real, eR1_test[0, 0].imag, eR1_test[0, 1].real, eR1_test[0, 1].imag, eR1_test[1, 0].real, eR1_test[1, 0].imag, eR1_test[1, 1].real, eR1_test[1, 1].imag))

        # Checks.
        for i in range(2):
            for j in range(2):
                self.assertAlmostEqual(eR1_test[i, j].real, eR1[i, j].real, 5)
                self.assertAlmostEqual(eR1_test[i, j].imag, eR1[i, j].imag, 5)
Example #5
0
def r2eff_ns_mmq_2site_mq(M0=None, F_vector=array([1, 0], float64), m1=None, m2=None, R20A=None, R20B=None, pA=None, pB=None, dw=None, dwH=None, k_AB=None, k_BA=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 m1:            A complex numpy matrix to be populated.
    @type m1:               numpy rank-2, 2D complex64 array
    @keyword m2:            A complex numpy matrix to be populated.
    @type m2:               numpy rank-2, 2D complex64 array
    @keyword R20A:          The transverse, spin-spin relaxation rate for state A.
    @type R20A:             float
    @keyword R20B:          The transverse, spin-spin relaxation rate for state B.
    @type R20B:             float
    @keyword pA:            The population of state A.
    @type pA:               float
    @keyword pB:            The population of state B.
    @type pB:               float
    @keyword dw:            The chemical exchange difference between states A and B in rad/s.
    @type dw:               float
    @keyword dwH:           The proton chemical exchange difference between states A and B in rad/s.
    @type dwH:              float
    @keyword k_AB:          The rate of exchange from site A to B (rad/s).
    @type k_AB:             float
    @keyword k_BA:          The rate of exchange from site B to A (rad/s).
    @type k_BA:             float
    @keyword inv_tcpmg:     The inverse of the total duration of the CPMG element (in inverse seconds).
    @type inv_tcpmg:        float
    @keyword tcp:           The tau_CPMG times (1 / 4.nu1).
    @type tcp:              numpy rank-1 float array
    @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 rank-1 float array
    @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:       int
    @keyword power:         The matrix exponential power array.
    @type power:            numpy int16, rank-1 array
    """

    # Populate the m1 and m2 matrices (only once per function call for speed).
    populate_matrix(matrix=m1, R20A=R20A, R20B=R20B, dw=-dw-dwH, k_AB=k_AB, k_BA=k_BA)     # D+ matrix component.
    populate_matrix(matrix=m2, R20A=R20A, R20B=R20B, dw=dw-dwH, k_AB=k_AB, k_BA=k_BA)    # Z- matrix component.

    # Loop over the time points, back calculating the R2eff values.
    for i in range(num_points):
        # The M1 and M2 matrices.
        M1 = matrix_exponential(m1*tcp[i])    # Equivalent to D+.
        M2 = matrix_exponential(m2*tcp[i])    # Equivalent to Z-.

        # The complex conjugates M1* and M2*
        M1_star = conj(M1)    # Equivalent to D+*.
        M2_star = conj(M2)    # Equivalent to Z-*.

        # Repetitive dot products (minimised for speed).
        M1_M2 = dot(M1, M2)
        M2_M1 = dot(M2, M1)
        M1_M2_M2_M1 = dot(M1_M2, M2_M1)
        M2_M1_M1_M2 = dot(M2_M1, M1_M2)
        M1_M2_star = dot(M1_star, M2_star)
        M2_M1_star = dot(M2_star, M1_star)
        M1_M2_M2_M1_star = dot(M1_M2_star, M2_M1_star)
        M2_M1_M1_M2_star = dot(M2_M1_star, M1_M2_star)

        # Special case of 1 CPMG block - the power is zero.
        if power[i] == 1:
            # M1.M2.
            A = M1_M2

            # M1*.M2*.
            B = M1_M2_star

            # M2.M1.
            C = M2_M1

            # M2*.M1*.
            D = M2_M1_star

        # 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 = square_matrix_power(M1_M2_M2_M1, fact)

            # (M2*.M1*.M1*.M2*)^(n/2).
            B = square_matrix_power(M2_M1_M1_M2_star, fact)

            # (M2.M1.M1.M2)^(n/2).
            C = square_matrix_power(M2_M1_M1_M2, fact)

            # (M1*.M2*.M2*.M1*)^(n/2).
            D = square_matrix_power(M1_M2_M2_M1_star, 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 = square_matrix_power(M1_M2_M2_M1, fact)
            A = dot(A, M1_M2)

            # (M1*.M2*.M2*.M1*)^((n-1)/2).M1*.M2*.
            B = square_matrix_power(M1_M2_M2_M1_star, fact)
            B = dot(B, M1_M2_star)

            # (M2.M1.M1.M2)^((n-1)/2).M2.M1.
            C = square_matrix_power(M2_M1_M1_M2, fact)
            C = dot(C, M2_M1)

            # (M2*.M1*.M1*.M2*)^((n-1)/2).M2*.M1*.
            D = square_matrix_power(M2_M1_M1_M2_star, fact)
            D = dot(D, M2_M1_star)

        # 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[i] = 1e99
        else:
            back_calc[i]= -inv_tcpmg * log(Mx / pA)
Example #6
0
def r2eff_ns_mmq_2site_sq_dq_zq(M0=None, F_vector=array([1, 0], float64), m1=None, m2=None, R20A=None, R20B=None, pA=None, pB=None, dw=None, dwH=None, k_AB=None, k_BA=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 m1:            A complex numpy matrix to be populated.
    @type m1:               numpy rank-2, 2D complex64 array
    @keyword m2:            A complex numpy matrix to be populated.
    @type m2:               numpy rank-2, 2D complex64 array
    @keyword R20A:          The transverse, spin-spin relaxation rate for state A.
    @type R20A:             float
    @keyword R20B:          The transverse, spin-spin relaxation rate for state B.
    @type R20B:             float
    @keyword pA:            The population of state A.
    @type pA:               float
    @keyword pB:            The population of state B.
    @type pB:               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:               float
    @keyword dwH:           Unused - this is simply to match the r2eff_ns_mmq_2site_mq() function arguments.
    @type dwH:              float
    @keyword k_AB:          The rate of exchange from site A to B (rad/s).
    @type k_AB:             float
    @keyword k_BA:          The rate of exchange from site B to A (rad/s).
    @type k_BA:             float
    @keyword inv_tcpmg:     The inverse of the total duration of the CPMG element (in inverse seconds).
    @type inv_tcpmg:        float
    @keyword tcp:           The tau_CPMG times (1 / 4.nu1).
    @type tcp:              numpy rank-1 float array
    @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 rank-1 float array
    @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:       int
    @keyword power:         The matrix exponential power array.
    @type power:            numpy int16, rank-1 array
    """

    # Populate the m1 and m2 matrices (only once per function call for speed).
    populate_matrix(matrix=m1, R20A=R20A, R20B=R20B, dw=dw, k_AB=k_AB, k_BA=k_BA)
    populate_matrix(matrix=m2, R20A=R20A, R20B=R20B, dw=-dw, k_AB=k_AB, k_BA=k_BA)

    # Loop over the time points, back calculating the R2eff values.
    for i in range(num_points):
        # The A+/- matrices.
        A_pos = matrix_exponential(m1*tcp[i])
        A_neg = matrix_exponential(m2*tcp[i])

        # The evolution for one n.
        evol_block = dot(A_pos, dot(A_neg, dot(A_neg, A_pos)))

        # The full evolution.
        evol = square_matrix_power(evol_block, 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[i] = 1e99
        else:
            back_calc[i] = -inv_tcpmg * log(Mx / pA)
def ns_r1rho_2site(M0=None, matrix=None, r1rho_prime=None, omega=None, offset=None, r1=0.0, pA=None, pB=None, dw=None, k_AB=None, k_BA=None, spin_lock_fields=None, relax_time=None, inv_relax_time=None, back_calc=None, num_points=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 float64, rank-1, 7D array
    @keyword matrix:            A numpy array to be populated to create the evolution matrix.
    @type matrix:               numpy rank-2, 6D float64 array
    @keyword r1rho_prime:       The R1rho_prime parameter value (R1rho with no exchange).
    @type r1rho_prime:          float
    @keyword omega:             The chemical shift for the spin in rad/s.
    @type omega:                float
    @keyword offset:            The spin-lock offsets for the data.
    @type offset:               numpy rank-1 float array
    @keyword r1:                The R1 relaxation rate.
    @type r1:                   float
    @keyword pA:                The population of state A.
    @type pA:                   float
    @keyword pB:                The population of state B.
    @type pB:                   float
    @keyword dw:                The chemical exchange difference between states A and B in rad/s.
    @type dw:                   float
    @keyword k_AB:              The rate of exchange from site A to B (rad/s).
    @type k_AB:                 float
    @keyword k_BA:              The rate of exchange from site B to A (rad/s).
    @type k_BA:                 float
    @keyword spin_lock_fields:  The R1rho spin-lock field strengths (in rad.s^-1).
    @type spin_lock_fields:     numpy rank-1 float array
    @keyword relax_time:        The total relaxation time period for each spin-lock field strength (in seconds).
    @type relax_time:           float
    @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:       float
    @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 rank-1 float array
    @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:           int
    """

    # Repetitive calculations (to speed up calculations).
    Wa = omega                  # Larmor frequency [s^-1].
    Wb = omega + dw             # Larmor frequency [s^-1].
    W = pA*Wa + pB*Wb           # Population-averaged Larmor frequency [s^-1].
    dA = Wa - offset            # Offset of spin-lock from A.
    dB = Wb - offset            # Offset of spin-lock from B.
    d = W - offset              # Offset of spin-lock from population-average.

    # Loop over the time points, back calculating the R2eff values.
    for i in range(num_points):
        # The matrix that contains all the contributions to the evolution, i.e. relaxation, exchange and chemical shift evolution.
        rr1rho_3d(matrix=matrix, R1=r1, r1rho_prime=r1rho_prime, pA=pA, pB=pB, wA=dA, wB=dB, w1=spin_lock_fields[i], k_AB=k_AB, k_BA=k_BA)

        # The following lines rotate the magnetization previous to spin-lock into the weff frame.
        theta = atan(spin_lock_fields[i]/dA)
        M0[0] = sin(theta)    # The A state initial X magnetisation.
        M0[2] = cos(theta)    # The A state initial Z magnetisation.

        # This matrix is a propagator that will evolve the magnetization with the matrix R.
        Rexpo = matrix_exponential(matrix*relax_time)

        # Magnetization evolution.
        MA = dot(M0, dot(Rexpo, M0))

        # The next lines calculate the R1rho using a two-point approximation, i.e. assuming that the decay is mono-exponential.
        if MA <= 0.0 or isNaN(MA):
            back_calc[i] = 1e99
        else:
            back_calc[i]= -inv_relax_time * log(MA)
def r2eff_ns_cpmg_2site_3D(r180x=None, M0=None, r10a=0.0, r10b=0.0, r20a=None, r20b=None, pA=None, pB=None, dw=None, k_AB=None, k_BA=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 float64, rank-1, 7D array
    @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:             float
    @keyword r20b:          The R2 value for state B in the absence of exchange.
    @type r20b:             float
    @keyword pA:            The population of state A.
    @type pA:               float
    @keyword pB:            The population of state B.
    @type pB:               float
    @keyword dw:            The chemical exchange difference between states A and B in rad/s.
    @type dw:               float
    @keyword k_AB:          The rate of exchange from site A to B (rad/s).
    @type k_AB:             float
    @keyword k_BA:          The rate of exchange from site B to A (rad/s).
    @type k_BA:             float
    @keyword inv_tcpmg:     The inverse of the total duration of the CPMG element (in inverse seconds).
    @type inv_tcpmg:        float
    @keyword tcp:           The tau_CPMG times (1 / 4.nu1).
    @type tcp:              numpy rank-1 float array
    @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 rank-1 float array
    @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:       int
    @keyword power:         The matrix exponential power array.
    @type power:            numpy int16, rank-1 array
    """

    # The matrix R that contains all the contributions to the evolution, i.e. relaxation, exchange and chemical shift evolution.
    R = rcpmg_3d(R1A=r10a, R1B=r10b, R2A=r20a, R2B=r20b, pA=pA, pB=pB, dw=dw, k_AB=k_AB, k_BA=k_BA)

    # Loop over the time points, back calculating the R2eff values.
    for i in range(num_points):
        # Initial magnetisation.
        Mint = M0

        # This matrix is a propagator that will evolve the magnetization with the matrix R for a delay tcp.
        Rexpo = matrix_exponential(R*tcp[i])

        # Loop over the CPMG elements, propagating the magnetisation.
        for j in range(2*power[i]):
            Mint = dot(Rexpo, Mint)
            Mint = dot(r180x, Mint)
            Mint = dot(Rexpo, Mint)

        # The next lines calculate the R2eff using a two-point approximation, i.e. assuming that the decay is mono-exponential.
        Mx = fabs(Mint[1] / pA)
        if Mx <= 0.0 or isNaN(Mx):
            back_calc[i] = 1e99
        else:
            back_calc[i]= -inv_tcpmg * log(Mx)
def r2eff_ns_cpmg_2site_star(Rr=None, Rex=None, RCS=None, R=None, M0=None, r20a=None, r20b=None, dw=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 Rr:            The matrix that contains only the R2 relaxation terms ("Redfield relaxation", i.e. non-exchange broadening).
    @type Rr:               numpy complex64, rank-2, 2D array
    @keyword Rex:           The matrix that contains the exchange terms between the two states A and B.
    @type Rex:              numpy complex64, rank-2, 2D array
    @keyword RCS:           The matrix that contains the chemical shift evolution.  It works here only with X magnetization, and the complex notation allows to evolve in the transverse plane (x, y).
    @type RCS:              numpy complex64, rank-2, 2D array
    @keyword R:             The matrix that contains all the contributions to the evolution, i.e. relaxation, exchange and chemical shift evolution.
    @type R:                numpy complex64, rank-2, 2D array
    @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:             float
    @keyword r20b:          The R2 value for state B in the absence of exchange.
    @type r20b:             float
    @keyword dw:            The chemical exchange difference between states A and B in rad/s.
    @type dw:               float
    @keyword inv_tcpmg:     The inverse of the total duration of the CPMG element (in inverse seconds).
    @type inv_tcpmg:        float
    @keyword tcp:           The tau_CPMG times (1 / 4.nu1).
    @type tcp:              numpy rank-1 float array
    @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 rank-1 float array
    @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:       int
    @keyword power:         The matrix exponential power array.
    @type power:            numpy int16, rank-1 array
    """

    # The matrix that contains only the R2 relaxation terms ("Redfield relaxation", i.e. non-exchange broadening).
    Rr[0, 0] = -r20a
    Rr[1, 1] = -r20b

    # The matrix that contains the chemical shift evolution.  It works here only with X magnetization, and the complex notation allows to evolve in the transverse plane (x, y).  The chemical shift for state A is assumed to be zero.
    RCS[1, 1] = complex(0.0, -dw)

    # The matrix R that contains all the contributions to the evolution, i.e. relaxation, exchange and chemical shift evolution.
    R = add(Rr, Rex)
    R = add(R, RCS)

    # This is the complex conjugate of the above.  It allows the chemical shift to run in the other direction, i.e. it is used to evolve the shift after a 180 deg pulse.  The factor of 2 is to minimise the number of multiplications for the prop_2 matrix calculation.
    cR2 = conj(R) * 2.0

    # Loop over the time points, back calculating the R2eff values.
    for i in range(num_points):
        # This matrix is a propagator that will evolve the magnetization with the matrix R for a delay tcp.
        eR_tcp = matrix_exponential(R*tcp[i])

        # 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 = dot(dot(eR_tcp, matrix_exponential(cR2*tcp[i])), eR_tcp)

        # 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 = square_matrix_power(prop_2, power[i])

        # 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[i] = 1e99
        else:
            back_calc[i]= -inv_tcpmg * log(Mx)