Structure Refinement: PbSO4, NPD + XRD¶
This example demonstrates a more advanced use of the EasyDiffraction library by explicitly creating and configuring sample models and experiments before adding them to a project. It could be more suitable for users who are interested in creating custom workflows. This tutorial provides minimal explanation and is intended for users already familiar with EasyDiffraction.
The tutorial covers a Rietveld refinement of PbSO4 crystal structure based on the joint fit of both X-ray and neutron diffraction data.
Import Library¶
In [2]:
Copied!
from easydiffraction import Experiment
from easydiffraction import Project
from easydiffraction import SampleModel
from easydiffraction import download_from_repository
from easydiffraction import Experiment
from easydiffraction import Project
from easydiffraction import SampleModel
from easydiffraction import download_from_repository
⚠️ 'pycrysfml' module not found. This calculation engine will not be available. ✅ 'cryspy' calculation engine is successfully imported. ✅ 'pdffit' calculation engine is successfully imported.
In [3]:
Copied!
model = SampleModel('pbso4')
model = SampleModel('pbso4')
Set Space Group¶
In [4]:
Copied!
model.space_group.name_h_m = 'P n m a'
model.space_group.name_h_m = 'P n m a'
Set Unit Cell¶
In [5]:
Copied!
model.cell.length_a = 8.47
model.cell.length_b = 5.39
model.cell.length_c = 6.95
model.cell.length_a = 8.47
model.cell.length_b = 5.39
model.cell.length_c = 6.95
Set Atom Sites¶
In [6]:
Copied!
model.atom_sites.add('Pb', 'Pb', 0.1876, 0.25, 0.167, b_iso=1.37)
model.atom_sites.add('S', 'S', 0.0654, 0.25, 0.684, b_iso=0.3777)
model.atom_sites.add('O1', 'O', 0.9082, 0.25, 0.5954, b_iso=1.9764)
model.atom_sites.add('O2', 'O', 0.1935, 0.25, 0.5432, b_iso=1.4456)
model.atom_sites.add('O3', 'O', 0.0811, 0.0272, 0.8086, b_iso=1.2822)
model.atom_sites.add('Pb', 'Pb', 0.1876, 0.25, 0.167, b_iso=1.37)
model.atom_sites.add('S', 'S', 0.0654, 0.25, 0.684, b_iso=0.3777)
model.atom_sites.add('O1', 'O', 0.9082, 0.25, 0.5954, b_iso=1.9764)
model.atom_sites.add('O2', 'O', 0.1935, 0.25, 0.5432, b_iso=1.4456)
model.atom_sites.add('O3', 'O', 0.0811, 0.0272, 0.8086, b_iso=1.2822)
In [7]:
Copied!
download_from_repository('d1a_pbso4.dat', destination='data')
download_from_repository('d1a_pbso4.dat', destination='data')
⚠️ Warning
File 'data/d1a_pbso4.dat' already exists and will not be overwritten.
Create Experiment¶
In [8]:
Copied!
expt1 = Experiment('npd', radiation_probe='neutron', data_path='data/d1a_pbso4.dat')
expt1 = Experiment('npd', radiation_probe='neutron', data_path='data/d1a_pbso4.dat')
Data loaded successfully
Experiment 🔬 'npd'. Number of data points: 1801
Set Instrument¶
In [9]:
Copied!
expt1.instrument.setup_wavelength = 1.91
expt1.instrument.calib_twotheta_offset = -0.1406
expt1.instrument.setup_wavelength = 1.91
expt1.instrument.calib_twotheta_offset = -0.1406
Set Peak Profile¶
In [10]:
Copied!
expt1.peak.broad_gauss_u = 0.139
expt1.peak.broad_gauss_v = -0.412
expt1.peak.broad_gauss_w = 0.386
expt1.peak.broad_lorentz_x = 0
expt1.peak.broad_lorentz_y = 0.088
expt1.peak.broad_gauss_u = 0.139
expt1.peak.broad_gauss_v = -0.412
expt1.peak.broad_gauss_w = 0.386
expt1.peak.broad_lorentz_x = 0
expt1.peak.broad_lorentz_y = 0.088
Set Background¶
Select the background type.
In [11]:
Copied!
expt1.background_type = 'line-segment'
expt1.background_type = 'line-segment'
Background type for experiment 'npd' changed to line-segment
Add background points.
In [12]:
Copied!
for x, y in [
(11.0, 206.1624),
(15.0, 194.75),
(20.0, 194.505),
(30.0, 188.4375),
(50.0, 207.7633),
(70.0, 201.7002),
(120.0, 244.4525),
(153.0, 226.0595),
]:
expt1.background.add(x, y)
for x, y in [
(11.0, 206.1624),
(15.0, 194.75),
(20.0, 194.505),
(30.0, 188.4375),
(50.0, 207.7633),
(70.0, 201.7002),
(120.0, 244.4525),
(153.0, 226.0595),
]:
expt1.background.add(x, y)
Set Linked Phases¶
In [13]:
Copied!
expt1.linked_phases.add('pbso4', scale=1.5)
expt1.linked_phases.add('pbso4', scale=1.5)
In [14]:
Copied!
download_from_repository('lab_pbso4.dat', destination='data')
download_from_repository('lab_pbso4.dat', destination='data')
⚠️ Warning
File 'data/lab_pbso4.dat' already exists and will not be overwritten.
Create Experiment¶
In [15]:
Copied!
expt2 = Experiment('xrd', radiation_probe='xray', data_path='data/lab_pbso4.dat')
expt2 = Experiment('xrd', radiation_probe='xray', data_path='data/lab_pbso4.dat')
Data loaded successfully
Experiment 🔬 'xrd'. Number of data points: 3601
Set Instrument¶
In [16]:
Copied!
expt2.instrument.setup_wavelength = 1.540567
expt2.instrument.calib_twotheta_offset = -0.05181
expt2.instrument.setup_wavelength = 1.540567
expt2.instrument.calib_twotheta_offset = -0.05181
Set Peak Profile¶
In [17]:
Copied!
expt2.peak.broad_gauss_u = 0.304138
expt2.peak.broad_gauss_v = -0.112622
expt2.peak.broad_gauss_w = 0.021272
expt2.peak.broad_lorentz_x = 0
expt2.peak.broad_lorentz_y = 0.057691
expt2.peak.broad_gauss_u = 0.304138
expt2.peak.broad_gauss_v = -0.112622
expt2.peak.broad_gauss_w = 0.021272
expt2.peak.broad_lorentz_x = 0
expt2.peak.broad_lorentz_y = 0.057691
Set Background¶
Select background type.
In [18]:
Copied!
expt2.background_type = 'chebyshev polynomial'
expt2.background_type = 'chebyshev polynomial'
Background type for experiment 'xrd' changed to chebyshev polynomial
Add background points.
In [19]:
Copied!
for x, y in [
(0, 119.195),
(1, 6.221),
(2, -45.725),
(3, 8.119),
(4, 54.552),
(5, -20.661),
]:
expt2.background.add(x, y)
for x, y in [
(0, 119.195),
(1, 6.221),
(2, -45.725),
(3, 8.119),
(4, 54.552),
(5, -20.661),
]:
expt2.background.add(x, y)
Set Linked Phases¶
In [20]:
Copied!
expt2.linked_phases.add('pbso4', scale=0.001)
expt2.linked_phases.add('pbso4', scale=0.001)
In [21]:
Copied!
project = Project()
project = Project()
Add Sample Model¶
In [22]:
Copied!
project.sample_models.add(model)
project.sample_models.add(model)
Add Experiments¶
In [23]:
Copied!
project.experiments.add(expt1)
project.experiments.add(expt2)
project.experiments.add(expt1)
project.experiments.add(expt2)
In [24]:
Copied!
project.analysis.current_calculator = 'cryspy'
project.analysis.current_calculator = 'cryspy'
Current calculator changed to
cryspy
Set Fit Mode¶
In [25]:
Copied!
project.analysis.fit_mode = 'joint'
project.analysis.fit_mode = 'joint'
Current fit mode changed to
joint
Set Minimizer¶
In [26]:
Copied!
project.analysis.current_minimizer = 'lmfit (leastsq)'
project.analysis.current_minimizer = 'lmfit (leastsq)'
Current minimizer changed to
lmfit (leastsq)
Set Fitting Parameters¶
Set sample model parameters to be optimized.
In [27]:
Copied!
model.cell.length_a.free = True
model.cell.length_b.free = True
model.cell.length_c.free = True
model.cell.length_a.free = True
model.cell.length_b.free = True
model.cell.length_c.free = True
Set experiment parameters to be optimized.
In [28]:
Copied!
expt1.linked_phases['pbso4'].scale.free = True
expt1.instrument.calib_twotheta_offset.free = True
expt1.peak.broad_gauss_u.free = True
expt1.peak.broad_gauss_v.free = True
expt1.peak.broad_gauss_w.free = True
expt1.peak.broad_lorentz_y.free = True
expt1.linked_phases['pbso4'].scale.free = True
expt1.instrument.calib_twotheta_offset.free = True
expt1.peak.broad_gauss_u.free = True
expt1.peak.broad_gauss_v.free = True
expt1.peak.broad_gauss_w.free = True
expt1.peak.broad_lorentz_y.free = True
In [29]:
Copied!
expt2.linked_phases['pbso4'].scale.free = True
expt2.instrument.calib_twotheta_offset.free = True
expt2.peak.broad_gauss_u.free = True
expt2.peak.broad_gauss_v.free = True
expt2.peak.broad_gauss_w.free = True
expt2.peak.broad_lorentz_y.free = True
for term in expt2.background:
term.coef.free = True
expt2.linked_phases['pbso4'].scale.free = True
expt2.instrument.calib_twotheta_offset.free = True
expt2.peak.broad_gauss_u.free = True
expt2.peak.broad_gauss_v.free = True
expt2.peak.broad_gauss_w.free = True
expt2.peak.broad_lorentz_y.free = True
for term in expt2.background:
term.coef.free = True
Perform Fit¶
In [30]:
Copied!
project.analysis.fit()
project.analysis.fit()
Using all experiments 🔬 ['npd', 'xrd'] for 'joint' fitting 🚀 Starting fit process with 'lmfit (leastsq)'... 📈 Goodness-of-fit (reduced χ²) change:
iteration | χ² | improvement [%] |
---|---|---|
1 |
37.01 |
|
25 |
16.30 |
56.0% ↓ |
136 |
16.21 |
🏆 Best goodness-of-fit (reduced χ²) is 16.21 at iteration 135 ✅ Fitting complete.
Fit results
✅ Success: True
⏱️ Fitting time: 7.47 seconds
📏 Goodness-of-fit (reduced χ²): 16.21
📏 R-factor (Rf): 13.18%
📏 R-factor squared (Rf²): 17.19%
📏 Weighted R-factor (wR): 17.09%
📈 Fitted parameters:
datablock | category | entry | parameter | start | fitted | uncertainty | units | change | |
---|---|---|---|---|---|---|---|---|---|
1 | pbso4 |
cell |
length_a |
8.4700 |
8.4693 |
0.0002 |
Å |
0.01 % ↓ |
|
2 | pbso4 |
cell |
length_b |
5.3900 |
5.3912 |
0.0001 |
Å |
0.02 % ↑ |
|
3 | pbso4 |
cell |
length_c |
6.9500 |
6.9504 |
0.0002 |
Å |
0.01 % ↑ |
|
4 | npd |
instrument |
twotheta_offset |
-0.1406 |
-0.1385 |
0.0019 |
deg |
1.47 % ↓ |
|
5 | npd |
linked_phases |
pbso4 |
scale |
1.5000 |
1.4622 |
0.0049 |
2.52 % ↓ |
|
6 | npd |
peak |
broad_gauss_u |
0.1390 |
0.2901 |
0.0350 |
deg² |
108.67 % ↑ |
|
7 | npd |
peak |
broad_gauss_v |
-0.4120 |
-0.6390 |
0.0531 |
deg² |
55.10 % ↑ |
|
8 | npd |
peak |
broad_gauss_w |
0.3860 |
0.4552 |
0.0186 |
deg² |
17.92 % ↑ |
|
9 | npd |
peak |
broad_lorentz_y |
0.0880 |
0.0931 |
0.0034 |
deg |
5.76 % ↑ |
|
10 | xrd |
background |
0 |
chebyshev_coef |
119.1950 |
85.8587 |
1.3293 |
27.97 % ↓ |
|
11 | xrd |
background |
1 |
chebyshev_coef |
6.2210 |
-19.3112 |
1.8805 |
410.42 % ↓ |
|
12 | xrd |
background |
2 |
chebyshev_coef |
-45.7250 |
5.3658 |
1.7778 |
111.74 % ↓ |
|
13 | xrd |
background |
3 |
chebyshev_coef |
8.1190 |
11.8842 |
1.7472 |
46.37 % ↑ |
|
14 | xrd |
background |
4 |
chebyshev_coef |
54.5520 |
18.5560 |
1.5670 |
65.98 % ↓ |
|
15 | xrd |
background |
5 |
chebyshev_coef |
-20.6610 |
-6.4093 |
1.5359 |
68.98 % ↓ |
|
16 | xrd |
instrument |
twotheta_offset |
-0.0518 |
-0.0637 |
0.0009 |
deg |
22.93 % ↑ |
|
17 | xrd |
linked_phases |
pbso4 |
scale |
0.0010 |
0.0010 |
0.0000 |
1.52 % ↓ |
|
18 | xrd |
peak |
broad_gauss_u |
0.3041 |
0.5724 |
0.0196 |
deg² |
88.21 % ↑ |
|
19 | xrd |
peak |
broad_gauss_v |
-0.1126 |
-0.2799 |
0.0142 |
deg² |
148.57 % ↑ |
|
20 | xrd |
peak |
broad_gauss_w |
0.0213 |
0.0452 |
0.0024 |
deg² |
112.27 % ↑ |
|
21 | xrd |
peak |
broad_lorentz_y |
0.0577 |
0.0587 |
0.0014 |
deg |
1.75 % ↑ |
Plot Measured vs Calculated¶
In [31]:
Copied!
project.plot_meas_vs_calc(expt_name='npd', x_min=35.5, x_max=38.3, show_residual=True)
project.plot_meas_vs_calc(expt_name='npd', x_min=35.5, x_max=38.3, show_residual=True)
In [32]:
Copied!
project.plot_meas_vs_calc(expt_name='xrd', x_min=29.0, x_max=30.4, show_residual=True)
project.plot_meas_vs_calc(expt_name='xrd', x_min=29.0, x_max=30.4, show_residual=True)