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 ExperimentFactory
from easydiffraction import Project
from easydiffraction import SampleModelFactory
from easydiffraction import download_from_repository
from easydiffraction import ExperimentFactory
from easydiffraction import Project
from easydiffraction import SampleModelFactory
from easydiffraction import download_from_repository
In [3]:
Copied!
model = SampleModelFactory.create(name='pbso4')
model = SampleModelFactory.create(name='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_from_args(
    label='Pb',
    type_symbol='Pb',
    fract_x=0.1876,
    fract_y=0.25,
    fract_z=0.167,
    wyckoff_letter='c',
    b_iso=1.37,
)
model.atom_sites.add_from_args(
    label='S',
    type_symbol='S',
    fract_x=0.0654,
    fract_y=0.25,
    fract_z=0.684,
    wyckoff_letter='c',
    b_iso=0.3777,
)
model.atom_sites.add_from_args(
    label='O1',
    type_symbol='O',
    fract_x=0.9082,
    fract_y=0.25,
    fract_z=0.5954,
    wyckoff_letter='c',
    b_iso=1.9764,
)
model.atom_sites.add_from_args(
    label='O2',
    type_symbol='O',
    fract_x=0.1935,
    fract_y=0.25,
    fract_z=0.5432,
    wyckoff_letter='c',
    b_iso=1.4456,
)
model.atom_sites.add_from_args(
    label='O3',
    type_symbol='O',
    fract_x=0.0811,
    fract_y=0.0272,
    fract_z=0.8086,
    wyckoff_letter='d',
    b_iso=1.2822,
)
model.atom_sites.add_from_args(
    label='Pb',
    type_symbol='Pb',
    fract_x=0.1876,
    fract_y=0.25,
    fract_z=0.167,
    wyckoff_letter='c',
    b_iso=1.37,
)
model.atom_sites.add_from_args(
    label='S',
    type_symbol='S',
    fract_x=0.0654,
    fract_y=0.25,
    fract_z=0.684,
    wyckoff_letter='c',
    b_iso=0.3777,
)
model.atom_sites.add_from_args(
    label='O1',
    type_symbol='O',
    fract_x=0.9082,
    fract_y=0.25,
    fract_z=0.5954,
    wyckoff_letter='c',
    b_iso=1.9764,
)
model.atom_sites.add_from_args(
    label='O2',
    type_symbol='O',
    fract_x=0.1935,
    fract_y=0.25,
    fract_z=0.5432,
    wyckoff_letter='c',
    b_iso=1.4456,
)
model.atom_sites.add_from_args(
    label='O3',
    type_symbol='O',
    fract_x=0.0811,
    fract_y=0.0272,
    fract_z=0.8086,
    wyckoff_letter='d',
    b_iso=1.2822,
)
In [7]:
Copied!
download_from_repository('d1a_pbso4.dat', destination='data')
download_from_repository('d1a_pbso4.dat', destination='data')
Downloading...
File 'd1a_pbso4.dat' from 'easyscience/diffraction-lib'
Create Experiment¶
In [8]:
Copied!
expt1 = ExperimentFactory.create(
    name='npd',
    data_path='data/d1a_pbso4.dat',
    radiation_probe='neutron',
)
expt1 = ExperimentFactory.create(
    name='npd',
    data_path='data/d1a_pbso4.dat',
    radiation_probe='neutron',
)
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_from_args(x=x, y=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_from_args(x=x, y=y)
Set Linked Phases¶
In [13]:
Copied!
expt1.linked_phases.add_from_args(id='pbso4', scale=1.5)
expt1.linked_phases.add_from_args(id='pbso4', scale=1.5)
In [14]:
Copied!
download_from_repository('lab_pbso4.dat', destination='data')
download_from_repository('lab_pbso4.dat', destination='data')
Downloading...
File 'lab_pbso4.dat' from 'easyscience/diffraction-lib'
Create Experiment¶
In [15]:
Copied!
expt2 = ExperimentFactory.create(
    name='xrd',
    data_path='data/lab_pbso4.dat',
    radiation_probe='xray',
)
expt2 = ExperimentFactory.create(
    name='xrd',
    data_path='data/lab_pbso4.dat',
    radiation_probe='xray',
)
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_from_args(order=x, coef=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_from_args(order=x, coef=y)
Set Linked Phases¶
In [20]:
Copied!
expt2.linked_phases.add_from_args(id='pbso4', scale=0.001)
expt2.linked_phases.add_from_args(id='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 | 1 | 37.01 | |
| 2 | 25 | 16.30 | 56.0% ↓ | 
| 3 | 136 | 16.21 | 
🏆 Best goodness-of-fit (reduced χ²) is 16.21 at iteration 135
✅ Fitting complete.
Fit results
✅ Success: True
⏱️ Fitting time: 12.36 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 | None | length_a | 8.4700 | 8.4693 | 0.0002 | Å | 0.01 % ↓ | 
| 2 | pbso4 | cell | None | length_b | 5.3900 | 5.3912 | 0.0001 | Å | 0.02 % ↑ | 
| 3 | pbso4 | cell | None | length_c | 6.9500 | 6.9504 | 0.0002 | Å | 0.01 % ↑ | 
| 4 | npd | peak | None | broad_gauss_u | 0.1390 | 0.2901 | 0.0350 | deg² | 108.67 % ↑ | 
| 5 | npd | peak | None | broad_gauss_v | -0.4120 | -0.6390 | 0.0531 | deg² | 55.10 % ↑ | 
| 6 | npd | peak | None | broad_gauss_w | 0.3860 | 0.4552 | 0.0186 | deg² | 17.92 % ↑ | 
| 7 | npd | peak | None | broad_lorentz_y | 0.0880 | 0.0931 | 0.0034 | deg | 5.76 % ↑ | 
| 8 | npd | linked_phases | pbso4 | scale | 1.5000 | 1.4622 | 0.0049 | 2.52 % ↓ | |
| 9 | npd | instrument | None | twotheta_offset | -0.1406 | -0.1385 | 0.0019 | deg | 1.47 % ↓ | 
| 10 | xrd | peak | None | broad_gauss_u | 0.3041 | 0.5724 | 0.0196 | deg² | 88.21 % ↑ | 
| 11 | xrd | peak | None | broad_gauss_v | -0.1126 | -0.2799 | 0.0142 | deg² | 148.57 % ↑ | 
| 12 | xrd | peak | None | broad_gauss_w | 0.0213 | 0.0452 | 0.0024 | deg² | 112.27 % ↑ | 
| 13 | xrd | peak | None | broad_lorentz_y | 0.0577 | 0.0587 | 0.0014 | deg | 1.75 % ↑ | 
| 14 | xrd | linked_phases | pbso4 | scale | 0.0010 | 0.0010 | 0.0000 | 1.52 % ↓ | |
| 15 | xrd | instrument | None | twotheta_offset | -0.0518 | -0.0637 | 0.0009 | deg | 22.93 % ↑ | 
| 16 | xrd | background | 0 | coef | 119.1950 | 85.8587 | 1.3293 | 27.97 % ↓ | |
| 17 | xrd | background | 1 | coef | 6.2210 | -19.3112 | 1.8805 | 410.42 % ↓ | |
| 18 | xrd | background | 2 | coef | -45.7250 | 5.3658 | 1.7778 | 111.74 % ↓ | |
| 19 | xrd | background | 3 | coef | 8.1190 | 11.8842 | 1.7472 | 46.37 % ↑ | |
| 20 | xrd | background | 4 | coef | 54.5520 | 18.5560 | 1.5670 | 65.98 % ↓ | |
| 21 | xrd | background | 5 | coef | -20.6610 | -6.4093 | 1.5359 | 68.98 % ↓ | 
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)