Bloch-McConnell Relaxation Dispersion#

In solution NMR (and also sometimes in solids), if a two-site exchange process leads to modulation of chemical shift, it is in some cases possible to calculate the rate of exchange (\(k_{ex}=k_{1\rightarrow2}+k_{2\rightarrow1}\)), the change in chemical shift (\(\Delta\omega=\Omega_1-\Omega_2\)) and also the populations of the two sites (\(p_1/p_2=k_{2\rightarrow1}/k_{1\rightarrow2}\)), based on a series of \(R_{1\rho}\) measurements. However, this depends on the rate of exchange and also the strength of the spin-lock. If the exchange is too fast, it is impossible to separate the populations from the change in chemical shift, obtaining only the product \(p_1p_2\Delta\omega_{12}^2\).

Furthermore, if only on-resonance \(R_{1\rho}\) experiments are applied, it is impossible to determine whether the larger population has the larger or smaller chemical shift, whereas this can be resolved using off-resonant \(R_{1\rho}\) experiments. We will investigate this with simulation, and also compare to the formula provided by Miloushev and Palmer.\(^1\)

Note that we also use \(R_{1\rho}\) in solids, but then we must also consider reorientational dynamics.

[1] V.Z. Miloushev, A.G. Palmer. J. Magn. Reson., 2015, 177, 221-227

Setup#

import SLEEPY as sl
import numpy as np
import matplotlib.pyplot as plt

Theoretical Background#

Miloushev and Palmer provide us with the relevant formula:

\[ \begin{equation} R_{1\rho}^{ex}=\frac{\sin^2\beta_ep_1p_2\Delta\omega_{12}^2k_{ex}}{\frac{\omega_{e1}^2\omega_{e2}^2}{\omega_e^2}+k_{ex}^2-\sin^2\beta_ep_1p_2\Delta\omega_{12}^2\left(1+\frac{2k_{ex}^2(p_1\omega_{e1}^2+p_2\omega_{e2}^2)}{\omega_{e1}^2\omega_{e2}^2+\omega_e^2k_{ex}^2}\right)} \end{equation} \]

with the following definitions:

\[\begin{split} \begin{eqnarray} k_{ex}&=&k_{1\rightarrow2}+k_{2\rightarrow1} \\ \Omega&=&p_1\Omega_1+p_2\Omega_2 \\ \Delta\omega_{12}&=&\Omega_1-\Omega_2 \\ \omega_e^2&=&\omega_1^2+\Omega^2 \\ \omega_{e1}^2&=&\omega_1^2+\Omega_1^2 \\ \omega_{e2}^2&=&\omega_1^2+\Omega_2^2 \\ \sin^2\beta_e&=&\frac{\omega_1^2}{\omega_1^2+\Omega^2} \end{eqnarray} \end{split}\]

In the above formula, the relative sizes of the three terms in the denominator determine the availability of information from \(R_{1\rho}\) experiments. For example, if either the field strength is set too high, or \(k_{ex}\) is too large, the third term can be neglected, yielding

\[ \begin{equation} R_{1\rho}^{ex}=\frac{\sin^2\beta_ep_1p_2\Delta\omega_{12}^2k_{ex}}{\frac{\omega_{e1}^2\omega_{e2}^2}{\omega_e^2}+k_{ex}^2} \end{equation} \]

In this case, \(p_1\) and \(p_2\) never appear separately from \(\Delta\omega_{12}\), so that we cannot separate their contributions based on experiment. We may control the applied field strength, which modifies \(\omega_{e1}^2\omega_{e2}^2/\omega_e^2\), but cannot control \(k_{ex}\).

Note that if the applied field strength is significantly larger than \(\Delta\omega_{12}\), then the last term of the denominator drops out, and the first term simplifies to \(\omega_e^2\), yielding

\[ \begin{equation} R_{1\rho}^{ex}=\frac{\sin^2\beta_ep_1p_2\Delta\omega_{12}^2k_{ex}}{\omega_e^2+k_{ex}^2} \end{equation} \]

In the simulations below, we will test some of these behaviors. First, we define a function that calculates the above formula, which we can use to compare to simulated results.

def R1p_fun(kex,v1,CS1,CS2,p1):
    p2=1-p1 #Population 2
    #Convert to rad/s
    omega1=v1*2*np.pi 
    Omega1=CS1*2*np.pi
    Omega2=CS2*2*np.pi
    
    Omega=p1*Omega1+p2*Omega2 #Average shift
    D_Omega=Omega1-Omega2  #Change in shift
    sin2beta=omega1**2/(omega1**2+Omega**2) #Sine of offset angle
    
    #Effective fields
    omega_e2=omega1**2+Omega**2
    omega_e2_1=omega1**2+Omega1**2
    omega_e2_2=omega1**2+Omega2**2
    
    x=sin2beta*p1*p2*D_Omega**2
    num=x*kex #Numerator
    den1=omega_e2_1*omega_e2_2/omega_e2 #Denominator (1st term)
    den2=kex**2 #Denominator (2nd term)
    den3=x*(1+2*kex**2*(p1*omega_e2_1+p2*omega_e2_2)/(omega_e2_1*omega_e2_2+omega_e2*kex**2))
    return num/(den1+den2-den3)

Define parameters, define the spin-system#

p1=0.75  #Population 1
p2=1-p1  #Population 2
kex=30000
tc=1/kex     #Correlation time
DelOmega12=500  #Change in Chemical Shift in Hz

ex0=sl.ExpSys(v0H=600,Nucs='13C',T_K=298) #We need a description of the experiment for both states (ex0, ex1)
ex1=ex0.copy()
ex0.set_inter(Type='CS',i=0,Hz=DelOmega12*p2)
_=ex1.set_inter(Type='CS',i=0,Hz=-DelOmega12*p1)

Build the Liouvillian, add the exchange matrix#

L=sl.Liouvillian((ex0,ex1))  #Builds the two different Hamiltonians and exports them to Liouville space
L.kex=sl.Tools.twoSite_kex(tc=tc,p1=p1) #Add exchange to the Liouvillian

print(f'The average chemical shift is {ex0.CS[0]["Hz"]*p1+ex1.CS[0]["Hz"]*p2:.2f} Hz')
#ex0.CS lists all chemical shifts and their parameters in the system
The average chemical shift is 0.00 Hz

Create a propagator with on-resonant spin-lock, calculate \(R_{1\rho}\) relaxation#

seq=L.Sequence(Dt=.0003)
v1=500
seq.add_channel('13C',v1=v1)  #Spin lock at -3.5 ppm (on-resonance)
U=seq.U()  #Propagator (10 ms)

rho=sl.Rho(rho0='13Cx',detect='13Cx')
n=300
rho.DetProp(U,n=n)

ax=rho.plot(axis='ms')
R1p=R1p_fun(kex=kex,v1=v1,CS1=ex0.CS[0]['Hz'],CS2=ex1.CS[0]['Hz'],p1=p1)
ax.plot(rho.t_axis*1e3,np.exp(-R1p*rho.t_axis),color='black',linestyle=':')
_=ax.legend(('Simulated','Calculated'))
../_images/a8df2fa85dfe3bb3db8b0b4df199a700b06a954dcc78f402b262e4e9d090878e.png

What happens if we vary the populations and \(\Delta\omega_{12}^2\), but leave \(p_1p_2\Delta\Omega^2\) fixed?#

We start out with fast exchange, i.e. \(k_{ex}>>\Delta\Omega_{12}\) (\(k_{ex}=\)30000 s\(^{-1}\), \(\Delta\Omega_{12}\)=500x2\(\pi\) Hz).

We claim above that for fast exchange, we cannot separate \(p_1\) and \(p_2\) from \(\Delta\Omega_{12}\) experimentally. We verify this by fixing the product \(p_1p_2\Delta\Omega_{12}^2\), but varying \(p_1\).

kex=30000
tc=1/kex
ax=plt.subplots(figsize=[8,6])[1]
p1p2Delv12=p1*p2*500**2  #p1*p2*DeltaOmega**2 is fixed below
p10=np.linspace(0.1,.9,9) #Sweep from p1=0.1 to p1=0.9
for p1 in p10:
    p2=1-p1 #Calculate p2
    Delv12=np.sqrt(p1p2Delv12/p1/p2) #Adjust DeltaOmega to keep product fixed
    
    ex0.set_inter(Type='CS',i=0,Hz=Delv12/2)
    ex1.set_inter(Type='CS',i=0,Hz=-Delv12/2)
    L=sl.Liouvillian((ex0,ex1))  #Builds the two different Hamiltonians and exports them to Liouville space
    L.kex=sl.Tools.twoSite_kex(tc=tc,p1=p1) #Add exchange to the Liouvillian
    
    seq=L.Sequence()
    v0=Delv12/2*p1-Delv12/2*p2
    seq.add_channel('13C',v1=500,voff=v0)  #On-resonant spin-lock
    U=seq.U(.0003)  #Propagator (10 ms)
    rho=sl.Rho(rho0='13Cx',detect='13Cx')
    rho.DetProp(U,n=n)
    rho.plot(axis='ms',ax=ax)
ax.legend([r'$p_1$ = '+f'{p1:.1f}' for p1 in p10])
_=ax.set_title(r'$\tau_c$ = '+f'{tc:.1e} s'+r', '+'$p_1p_2(\Delta\Omega_{12})^2$ fixed')
../_images/ec2833afea4d08f6b8bf5712b1f25239be89850ac1a090d200cc1f08a9c5bb5a.png

As we can see above, all relaxation curves are nearly identical, preventing us from separating the populations from the change in chemical shift.

On the other hand, if exchange is slower, the curves will separate as we vary the populations. In order to demonstrate this, we reduce \(k_{ex}\) to 800 s\(^{-1}\).

kex=800
tc=1/kex
ax=plt.subplots(figsize=[8,6])[1]
for p1 in p10:
    p2=1-p1 #Calculate p2
    Delv12=np.sqrt(p1p2Delv12/p1/p2) #Adjust DeltaOmega to keep product fixed
    ex0.set_inter(Type='CS',i=0,Hz=Delv12/2)
    ex1.set_inter(Type='CS',i=0,Hz=-Delv12/2)
    L=sl.Liouvillian((ex0,ex1))  #Builds the two different Hamiltonians and exports them to Liouville space
    L.kex=sl.Tools.twoSite_kex(tc=tc,p1=p1) #Add exchange to the Liouvillian
    seq=L.Sequence()
    v0=Delv12/2*p1-Delv12/2*p2
    seq.add_channel('13C',v1=v1,voff=v0)  #On-resonant spin-lock
    U=seq.U(.0003)  #Propagator (10 ms)
    rho=sl.Rho(rho0='13Cx',detect='13Cx')
    rho.DetProp(U,n=n)
    rho.plot(axis='ms',ax=ax)
ax.legend([r'$p_1$ = '+f'{p1:.1f}' for p1 in p10])
_=ax.set_title(r'$\tau_c$ = '+f'{tc:.1e} s'+r', '+'$p_1p_2(\Delta\Omega_{12})^2$ fixed')
../_images/e536f771897c49a224f7d73ad77025bf012eb6ed2079853e4d14c53ac8f2c016.png

For slower exchange, the third term in the denominator becomes important, yielding differences in relaxation depending on the population.

In the last example, we increase \(\nu_1\) to 5 kHz, but leave the exchange rate fixed. Relaxation also slows down, so we acquire longer.

v1=5000
ax=plt.subplots(figsize=[8,6])[1]
for p1 in p10:
    p2=1-p1 #Calculate p2
    Delv12=np.sqrt(p1p2Delv12/p1/p2) #Adjust DeltaOmega to keep product fixed
    ex0.set_inter(Type='CS',i=0,Hz=Delv12/2)
    ex1.set_inter(Type='CS',i=0,Hz=-Delv12/2)
    L=sl.Liouvillian((ex0,ex1))  #Builds the two different Hamiltonians and exports them to Liouville space
    L.kex=sl.Tools.twoSite_kex(tc=tc,p1=p1) #Add exchange to the Liouvillian
    seq=L.Sequence()
    v0=Delv12/2*p1-Delv12/2*p2
    seq.add_channel('13C',v1=v1,voff=v0)  #On-resonant spin-lock
    U=seq.U(.0003)  #Propagator (10 ms)
    rho=sl.Rho(rho0='13Cx',detect='13Cx')
    rho.DetProp(U,n=n*20)
    rho.plot(axis='ms',ax=ax)
ax.legend([r'$p_1$ = '+f'{p1:.1f}' for p1 in p10])
_=ax.set_title(r'$\tau_c$ = '+f'{tc:.1e} s'+r', '+'$p_1p_2(\Delta\Omega_{12})^2$ fixed')
../_images/4e60ec369e627823c2d896c732dc143a650981bbce7f2fee275b6e0e43e771c8.png

The stronger field slows down the relaxation considerably, but furthermore, we again cannot separate \(p_1p_2\) from \(\Delta\Omega_{12}\). This can present challenges using BMRD in solid-state NMR, where we need to be able to apply low fields while retaining a spin-lock. Since other large, anisotropic interactions may be present in solids, a larger spin-lock may be required unless we can spin quickly and decrease the \(^1\)H concentration to avoid higher order effects.

In the case of slow exchange, with a sufficiently low spin-lock field strength, we can separate the population from \(\Delta\Omega_{12}\). However, we cannot say with an on-resonant spin-lock whether the larger population corresponds to the larger or smaller chemical shift. Then, we must apply an off-resonant spin-lock.

Therefore, we simulate the behavior of relaxation as a function of offset frequency. First, we calculate a spectrum to see where the peak actually appears (and also mark where the original two peaks would be in the absence of exchange).

ax=plt.subplots()[1]
kex=2000
tc=1/kex
p1=0.75
p2=1-p1
Delv12=np.sqrt(p1p2Delv12/p1/p2)

ex0.set_inter(Type='CS',i=0,Hz=Delv12*p2)
ex1.set_inter(Type='CS',i=0,Hz=-Delv12*p1)

L=sl.Liouvillian((ex0,ex1))  #Builds the two different Hamiltonians and exports them to Liouville space
L.kex=sl.Tools.twoSite_kex(tc=tc,p1=p1) #Add exchange to the Liouvillian
U=L.U(.0003)  #Propagator (10 ms)
rho0=sl.Rho(rho0='13Cx',detect='13Cp')
rho0.DetProp(U,n=1000)
rho0.plot(axis='Hz',ax=ax,FT=True)
ax.set_ylim(ax.get_ylim())
ax.plot(ex0.CS[0]['Hz']*np.ones(2),ax.get_ylim(),color='black',linestyle=':')
_=ax.plot(ex1.CS[0]['Hz']*np.ones(2),ax.get_ylim(),color='black',linestyle=':')
../_images/cdbd3077ce3337055ffac76b396d789d787aecb43fc72f12a8a7ea9fbc43be7d.png

Application of off-resonance fields results in an effective field that is larger than the applied field (\(\omega_{e}^2=\omega_1^2+\Delta\Omega^2\)). The larger effective field results in slower relaxation. However, if the exchange is sufficiently slow, it also matters how large the effective field is relative to each of the two resonance frequencies, resulting in different relaxation behavior depending on if the off-resonance field is above or below the mean frequency (\(\Omega=p_1\Omega_1+p_2\Omega_2\)).

v1=500
voff0=np.linspace(0,750,5)

fig,ax=plt.subplots(2,len(voff0),figsize=[10,5])
rho=sl.Rho(rho0='13Cz',detect='13Cp')
Dt=.0003
for voff,a in zip(voff0,ax.T):

    # Here we just plot a spectrum plus mark where the offsets are placed
    rho0.plot(FT=True,ax=a[0],axis='Hz',color='black')
    yl=a[0].get_ylim()
    a[0].plot(voff*np.ones(2),yl,color='red')
    a[0].plot(-voff*np.ones(2),yl,color='dodgerblue',linestyle=':')
    a[0].set_ylim(yl)
    
    # If we flip the right amount for the off-resonant field, we reduce oscillation
    flip=np.arcsin(500/np.sqrt(500**2+voff**2)) #Flip angle
    # Flip from z
    Uflip0=L.Sequence().add_channel('13C',v1=100000,t=[0,flip/100000/2/np.pi],phase=np.pi/2).U()
    Uflip1=L.Sequence().add_channel('13C',v1=100000,t=[0,(np.pi/2-flip)/100000/2/np.pi+1e-10],phase=np.pi/2).U()
    rho.clear()
                   
    seq=L.Sequence().add_channel('13C',v1=v1,phase=-np.pi,voff=voff)
    U=seq.U(Dt)
    for n in range(100):
        rho.reset()
        #Partial flip down, off-resonance spin-lock, finish flip, detect
        (Uflip1*(U**n)*Uflip0*rho)() 
    rho.plot(axis='ms',ax=a[1],color='red')
    
    seq=L.Sequence().add_channel('13C',v1=v1,voff=-voff)
    U=seq.U(Dt)
    
    rho.clear()
    for n in range(100):
        rho.reset()
        #Partial flip down, off-resonance spin-lock, finish flip, detect
        (Uflip1*(U**n)*Uflip0*rho)()
    rho.plot(axis='ms',ax=a[1],color='dodgerblue',linestyle=':')
    
    # Make the plots nicer
    if not(a[0].is_first_col()):
        a[0].set_ylabel('')
        a[0].set_yticklabels('')
        a[1].set_ylabel('')
        a[1].set_yticklabels('')
fig.tight_layout()
../_images/b521a782371ab8ece1d528af1c7bf6a6883433be2b84c9c41c2de98b5bdb8d00.png

Then, we see that relaxation is faster when the off-resonant field is applied towards the chemical shift corresponding to the smaller population. In this case, we are less off-resonant relative to the two chemical shifts, resulting in smaller effective fields and less relaxation.

In the final example, we go back to the fast-exchange regime, and observe that going off-resonance no longer helps distinguish which population corresponds to the higher or lower chemical shift.

kex=20000
tc=1/kex
L.kex=sl.Tools.twoSite_kex(tc=tc,p1=p1) #Add exchange to the Liouvillian

voff0=np.linspace(0,750,5)
fig,ax=plt.subplots(2,len(voff0),figsize=[10,5])
rho=sl.Rho(rho0='13Cz',detect='13Cp')
Dt=.001
for voff,a in zip(voff0,ax.T):
    a[1].set_ylim([0,.51])
    rho0.plot(FT=True,ax=a[0],axis='Hz',color='black')
    yl=a[0].get_ylim()
    a[0].plot(voff*np.ones(2),yl,color='red')
    a[0].plot(-voff*np.ones(2),yl,color='dodgerblue',linestyle=':')
    a[0].set_ylim(yl)
    flip=np.arcsin(500/np.sqrt(500**2+voff**2))
    Uflip0=L.Sequence().add_channel('13C',v1=100000,t=[0,flip/100000/2/np.pi],phase=np.pi/2).U()
    Uflip1=L.Sequence().add_channel('13C',v1=100000,t=[0,(np.pi/2-flip)/100000/2/np.pi+1e-10],phase=np.pi/2).U()
    rho.clear()
    
                   
    seq=L.Sequence().add_channel('13C',v1=-500,voff=voff)
    U=seq.U(Dt)
    for n in range(100):
        rho.reset()
        (Uflip1*(U**n)*Uflip0*rho)()
    rho.plot(axis='ms',ax=a[1],color='red')
    
    seq=L.Sequence().add_channel('13C',v1=500,voff=-voff)
    U=seq.U(Dt)
    
    rho.clear()
    for n in range(100):
        rho.reset()
        (Uflip1*(U**n)*Uflip0*rho)()
    rho.plot(axis='ms',ax=a[1],color='dodgerblue',linestyle=':')
    
    if not(a[0].is_first_col()):
        a[0].set_ylabel('')
        a[0].set_yticklabels('')
        a[1].set_ylabel('')
        a[1].set_yticklabels('')
fig.tight_layout()      
../_images/80d2856c2a520956a39815ff6d6565534419bb8e56f767d3e21364fa5553fc5c.png