Note
Click here to download the full example code or to run this example in your browser via Binder
¹³C MAS NMR of Glycine (CSA) [1940 Hz]¶
The following is a sideband least-squares fitting example of a \(^{13}\text{C}\) MAS NMR spectrum of Glycine spinning at 1940 Hz. The following experimental dataset is a part of DMFIT 1 examples. We thank Dr. Dominique Massiot for sharing the dataset.
import csdmpy as cp
import matplotlib.pyplot as plt
from lmfit import Minimizer, report_fit
from mrsimulator import Simulator, SpinSystem, Site
from mrsimulator.methods import BlochDecaySpectrum
from mrsimulator import signal_processing as sp
from mrsimulator.utils import spectral_fitting as sf
from mrsimulator.utils import get_spectral_dimensions
Import the dataset¶
host = "https://nmr.cemhti.cnrs-orleans.fr/Dmfit/Help/csdm/"
filename = "13C MAS 1940Hz - Glycine.csdf"
experiment = cp.load(host + filename)
# standard deviation of noise from the dataset
sigma = 3.822249
# For spectral fitting, we only focus on the real part of the complex dataset
experiment = experiment.real
# Convert the coordinates along each dimension from Hz to ppm.
_ = [item.to("ppm", "nmr_frequency_ratio") for item in experiment.dimensions]
# plot of the dataset.
plt.figure(figsize=(8, 4))
ax = plt.subplot(projection="csdm")
ax.plot(experiment, color="black", linewidth=0.5, label="Experiment")
ax.set_xlim(280, -10)
plt.grid()
plt.tight_layout()
plt.show()

Create a fitting model¶
Spin System
C1 = Site(
isotope="13C",
isotropic_chemical_shift=176.0, # in ppm
shielding_symmetric={"zeta": 70, "eta": 0.6}, # zeta in Hz
)
C2 = Site(
isotope="13C",
isotropic_chemical_shift=43.0, # in ppm
shielding_symmetric={"zeta": 30, "eta": 0.5}, # zeta in Hz
)
spin_systems = [SpinSystem(sites=[C1], name="C1"), SpinSystem(sites=[C2], name="C2")]
Method
# Get the spectral dimension parameters from the experiment.
spectral_dims = get_spectral_dimensions(experiment)
MAS = BlochDecaySpectrum(
channels=["13C"],
magnetic_flux_density=7.05, # in T
rotor_frequency=1940, # in Hz
spectral_dimensions=spectral_dims,
experiment=experiment, # experimental dataset
)
# Optimize the script by pre-setting the transition pathways for each spin system from
# the method.
for sys in spin_systems:
sys.transition_pathways = MAS.get_transition_pathways(sys)
Guess Model Spectrum
# Simulation
# ----------
sim = Simulator(spin_systems=spin_systems, methods=[MAS])
sim.config.decompose_spectrum = "spin_system"
sim.run()
# Post Simulation Processing
# --------------------------
processor = sp.SignalProcessor(
operations=[
sp.IFFT(),
sp.apodization.Exponential(FWHM="20 Hz", dv_index=0), # spin system 0
sp.apodization.Exponential(FWHM="200 Hz", dv_index=1), # spin system 1
sp.FFT(),
sp.Scale(factor=100),
]
)
processed_data = processor.apply_operations(data=sim.methods[0].simulation).real
# Plot of the guess Spectrum
# --------------------------
plt.figure(figsize=(8, 4))
ax = plt.subplot(projection="csdm")
ax.plot(experiment, color="black", linewidth=0.5, label="Experiment")
ax.plot(processed_data, linewidth=2, alpha=0.6)
ax.set_xlim(280, -10)
plt.grid()
plt.legend()
plt.tight_layout()
plt.show()

Least-squares minimization with LMFIT¶
Use the make_LMFIT_params() for a quick
setup of the fitting parameters.
params = sf.make_LMFIT_params(sim, processor, include={"rotor_frequency"})
params["sys_1_site_0_shielding_symmetric_eta"].vary = False
print(params.pretty_print(columns=["value", "min", "max", "vary", "expr"]))
Out:
Name Value Min Max Vary Expr
SP_0_operation_1_Exponential_FWHM 20 -inf inf True None
SP_0_operation_2_Exponential_FWHM 200 -inf inf True None
SP_0_operation_4_Scale_factor 100 -inf inf True None
mth_0_rotor_frequency 1940 1840 2040 True None
sys_0_abundance 50 0 100 True None
sys_0_site_0_isotropic_chemical_shift 176 -inf inf True None
sys_0_site_0_shielding_symmetric_eta 0.6 0 1 True None
sys_0_site_0_shielding_symmetric_zeta 70 -inf inf True None
sys_1_abundance 50 0 100 False 100-sys_0_abundance
sys_1_site_0_isotropic_chemical_shift 43 -inf inf True None
sys_1_site_0_shielding_symmetric_eta 0.5 0 1 False None
sys_1_site_0_shielding_symmetric_zeta 30 -inf inf True None
None
Solve the minimizer using LMFIT
minner = Minimizer(sf.LMFIT_min_function, params, fcn_args=(sim, processor, sigma))
result = minner.minimize()
report_fit(result)
Out:
[[Fit Statistics]]
# fitting method = leastsq
# function evals = 78
# data points = 4096
# variables = 10
chi-square = 5247.81571
reduced chi-square = 1.28434060
Akaike info crit = 1034.99309
Bayesian info crit = 1098.17075
[[Variables]]
sys_0_site_0_isotropic_chemical_shift: 176.133538 +/- 7.7530e-04 (0.00%) (init = 176)
sys_0_site_0_shielding_symmetric_zeta: 72.6573659 +/- 0.18735813 (0.26%) (init = 70)
sys_0_site_0_shielding_symmetric_eta: 0.92348112 +/- 0.00507035 (0.55%) (init = 0.6)
sys_0_abundance: 57.5381144 +/- 0.21265458 (0.37%) (init = 50)
sys_1_site_0_isotropic_chemical_shift: 43.3604050 +/- 0.00500134 (0.01%) (init = 43)
sys_1_site_0_shielding_symmetric_zeta: 23.1190164 +/- 0.50113151 (2.17%) (init = 30)
sys_1_site_0_shielding_symmetric_eta: 0.5 (fixed)
sys_1_abundance: 42.4618856 +/- 0.21265458 (0.50%) == '100-sys_0_abundance'
mth_0_rotor_frequency: 1940.88271 +/- 0.05721410 (0.00%) (init = 1940)
SP_0_operation_1_Exponential_FWHM: 29.6025777 +/- 0.17069359 (0.58%) (init = 20)
SP_0_operation_2_Exponential_FWHM: 134.355218 +/- 1.06603885 (0.79%) (init = 200)
SP_0_operation_4_Scale_factor: 170.972529 +/- 0.68520995 (0.40%) (init = 100)
[[Correlations]] (unreported correlations are < 0.100)
C(sys_0_abundance, sys_1_site_0_shielding_symmetric_zeta) = -0.606
C(sys_1_site_0_shielding_symmetric_zeta, SP_0_operation_4_Scale_factor) = 0.555
C(sys_0_abundance, SP_0_operation_2_Exponential_FWHM) = -0.446
C(sys_0_abundance, SP_0_operation_4_Scale_factor) = -0.442
C(sys_0_site_0_shielding_symmetric_zeta, sys_0_site_0_shielding_symmetric_eta) = -0.420
C(SP_0_operation_2_Exponential_FWHM, SP_0_operation_4_Scale_factor) = 0.409
C(SP_0_operation_1_Exponential_FWHM, SP_0_operation_4_Scale_factor) = 0.395
C(sys_0_abundance, SP_0_operation_1_Exponential_FWHM) = 0.319
C(sys_0_site_0_shielding_symmetric_zeta, SP_0_operation_4_Scale_factor) = 0.144
C(sys_0_site_0_shielding_symmetric_zeta, sys_0_abundance) = 0.122
The best fit solution¶
best_fit = sf.bestfit(sim, processor)[0]
residuals = sf.residuals(sim, processor)[0]
# Plot the spectrum
plt.figure(figsize=(8, 4))
ax = plt.subplot(projection="csdm")
ax.plot(experiment, color="black", linewidth=0.5, label="Experiment")
ax.plot(residuals, color="gray", linewidth=0.5, label="Residual")
ax.plot(best_fit, linewidth=2, alpha=0.6)
ax.set_xlim(280, -10)
plt.grid()
plt.legend()
plt.tight_layout()
plt.show()

- 1
D.Massiot, F.Fayon, M.Capron, I.King, S.Le Calvé, B.Alonso, J.O.Durand, B.Bujoli, Z.Gan, G.Hoatson, ‘Modelling one and two-dimensional solid-state NMR spectra.’, Magn. Reson. Chem. 40 70-76 (2002) DOI: 10.1002/mrc.984
Total running time of the script: ( 0 minutes 3.584 seconds)