Structure Refinement: PbSO4, XRD¶
This example demonstrates a more advanced use of the EasyDiffraction library by explicitly creating and configuring structures 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 laboratory X-ray powder diffraction data.
🛠️ Import Library¶
In [2]:
Copied!
from easydiffraction import ExperimentFactory
from easydiffraction import Project
from easydiffraction import StructureFactory
from easydiffraction import download_data
from easydiffraction import ExperimentFactory
from easydiffraction import Project
from easydiffraction import StructureFactory
from easydiffraction import download_data
In [3]:
Copied!
struct = StructureFactory.from_scratch(name='pbso4')
struct = StructureFactory.from_scratch(name='pbso4')
Set Space Group¶
In [4]:
Copied!
struct.space_group.name_h_m = 'P n m a'
struct.space_group.name_h_m = 'P n m a'
Set Unit Cell¶
In [5]:
Copied!
struct.cell.length_a = 8.48
struct.cell.length_b = 5.40
struct.cell.length_c = 6.96
struct.cell.length_a = 8.48
struct.cell.length_b = 5.40
struct.cell.length_c = 6.96
Set Atom Sites¶
In [6]:
Copied!
struct.atom_sites.create(
id='Pb',
type_symbol='Pb',
fract_x=0.1876,
fract_y=0.25,
fract_z=0.167,
adp_type='Biso',
adp_iso=1.37,
)
struct.atom_sites.create(
id='S',
type_symbol='S',
fract_x=0.0654,
fract_y=0.25,
fract_z=0.684,
adp_type='Biso',
adp_iso=0.3796,
)
struct.atom_sites.create(
id='O1',
type_symbol='O',
fract_x=0.9082,
fract_y=0.25,
fract_z=0.5954,
adp_type='Biso',
adp_iso=1.9840,
)
struct.atom_sites.create(
id='O2',
type_symbol='O',
fract_x=0.1935,
fract_y=0.25,
fract_z=0.5432,
adp_type='Biso',
adp_iso=1.4383,
)
struct.atom_sites.create(
id='O3',
type_symbol='O',
fract_x=0.0811,
fract_y=0.0272,
fract_z=0.8086,
adp_type='Biso',
adp_iso=1.2808,
)
struct.atom_sites.create(
id='Pb',
type_symbol='Pb',
fract_x=0.1876,
fract_y=0.25,
fract_z=0.167,
adp_type='Biso',
adp_iso=1.37,
)
struct.atom_sites.create(
id='S',
type_symbol='S',
fract_x=0.0654,
fract_y=0.25,
fract_z=0.684,
adp_type='Biso',
adp_iso=0.3796,
)
struct.atom_sites.create(
id='O1',
type_symbol='O',
fract_x=0.9082,
fract_y=0.25,
fract_z=0.5954,
adp_type='Biso',
adp_iso=1.9840,
)
struct.atom_sites.create(
id='O2',
type_symbol='O',
fract_x=0.1935,
fract_y=0.25,
fract_z=0.5432,
adp_type='Biso',
adp_iso=1.4383,
)
struct.atom_sites.create(
id='O3',
type_symbol='O',
fract_x=0.0811,
fract_y=0.0272,
fract_z=0.8086,
adp_type='Biso',
adp_iso=1.2808,
)
In [7]:
Copied!
data_path = download_data('meas-pbso4-xray', destination='data')
data_path = download_data('meas-pbso4-xray', destination='data')
Getting data...
Data 'meas-pbso4-xray': PbSO4, laboratory X-ray
✅ Data 'meas-pbso4-xray' downloaded to '../../../data/meas-pbso4-xray.xys'
Create Experiment¶
In [8]:
Copied!
expt = ExperimentFactory.from_data_path(
name='xrd',
data_path=data_path,
radiation_probe='xray',
)
expt = ExperimentFactory.from_data_path(
name='xrd',
data_path=data_path,
radiation_probe='xray',
)
Set Instrument¶
In [9]:
Copied!
expt.instrument.setup_wavelength = 1.540560
expt.instrument.setup_wavelength_2 = 1.544400
expt.instrument.setup_wavelength_2_to_1_ratio = 0.5
expt.instrument.setup_polarization_coefficient = 0.58
expt.instrument.setup_monochromator_twotheta = 28
expt.instrument.calib_twotheta_offset = -0.02
expt.instrument.setup_wavelength = 1.540560
expt.instrument.setup_wavelength_2 = 1.544400
expt.instrument.setup_wavelength_2_to_1_ratio = 0.5
expt.instrument.setup_polarization_coefficient = 0.58
expt.instrument.setup_monochromator_twotheta = 28
expt.instrument.calib_twotheta_offset = -0.02
Set Peak Profile¶
In [10]:
Copied!
expt.peak.type = 'pseudo-voigt + berar-baldinozzi asymmetry'
expt.peak.type = 'pseudo-voigt + berar-baldinozzi asymmetry'
⚠️ Switching peak profile type adds these settings with defaults: • asym_beba_a0=0.0 • asym_beba_a1=0.0 • asym_beba_b0=0.0 • asym_beba_b1=0.0
Peak profile type for experiment 'xrd' changed to
pseudo-voigt + berar-baldinozzi asymmetry
In [11]:
Copied!
expt.peak.broad_gauss_u = 0.03
expt.peak.broad_gauss_v = -0.04
expt.peak.broad_gauss_w = 0.01
expt.peak.broad_lorentz_y = 0.06
expt.peak.asym_beba_a0 = -0.23
expt.peak.asym_beba_b0 = -0.03
expt.peak.cutoff_fwhm = 6
expt.peak.broad_gauss_u = 0.03
expt.peak.broad_gauss_v = -0.04
expt.peak.broad_gauss_w = 0.01
expt.peak.broad_lorentz_y = 0.06
expt.peak.asym_beba_a0 = -0.23
expt.peak.asym_beba_b0 = -0.03
expt.peak.cutoff_fwhm = 6
Set Excluded Regions¶
In [12]:
Copied!
expt.excluded_regions.create(id='1', start=0, end=15)
expt.excluded_regions.create(id='2', start=160, end=180)
expt.excluded_regions.create(id='1', start=0, end=15)
expt.excluded_regions.create(id='2', start=160, end=180)
Set Background¶
Select background type.
In [13]:
Copied!
expt.background.type = 'chebyshev'
expt.background.type = 'chebyshev'
Background type for experiment 'xrd' changed to
chebyshev
Add Chebyshev background terms.
In [14]:
Copied!
for id, x, y in [
('1', 0, 149.0),
('2', 1, 67.0),
('3', 2, 9.0),
('4', 3, 10.0),
('5', 4, -5.0),
('6', 5, -9.0),
]:
expt.background.create(id=id, order=x, coef=y)
for id, x, y in [
('1', 0, 149.0),
('2', 1, 67.0),
('3', 2, 9.0),
('4', 3, 10.0),
('5', 4, -5.0),
('6', 5, -9.0),
]:
expt.background.create(id=id, order=x, coef=y)
Set Linked Structures¶
In [15]:
Copied!
expt.linked_structures.create(structure_id='pbso4', scale=0.001)
expt.linked_structures.create(structure_id='pbso4', scale=0.001)
In [16]:
Copied!
project = Project(name='pbso4_xray')
project = Project(name='pbso4_xray')
Add Structure¶
In [17]:
Copied!
project.structures.add(struct)
project.structures.add(struct)
Add Experiment¶
In [18]:
Copied!
project.experiments.add(expt)
project.experiments.add(expt)
🚀 Perform Analysis¶
This section outlines the analysis process, including how to configure calculation and fitting engines.
Set Free Parameters¶
Set structure parameters to be optimized.
In [19]:
Copied!
struct.cell.length_a.free = True
struct.cell.length_b.free = True
struct.cell.length_c.free = True
struct.cell.length_a.free = True
struct.cell.length_b.free = True
struct.cell.length_c.free = True
Set experiment parameters to be optimized.
In [20]:
Copied!
expt.linked_structures['pbso4'].scale.free = True
expt.instrument.calib_twotheta_offset.free = True
expt.peak.broad_gauss_u.free = True
expt.peak.broad_gauss_v.free = True
expt.peak.broad_gauss_w.free = True
expt.peak.broad_lorentz_y.free = True
expt.peak.asym_beba_a0.free = True
expt.peak.asym_beba_b0.free = True
for term in expt.background:
term.coef.free = True
expt.linked_structures['pbso4'].scale.free = True
expt.instrument.calib_twotheta_offset.free = True
expt.peak.broad_gauss_u.free = True
expt.peak.broad_gauss_v.free = True
expt.peak.broad_gauss_w.free = True
expt.peak.broad_lorentz_y.free = True
expt.peak.asym_beba_a0.free = True
expt.peak.asym_beba_b0.free = True
for term in expt.background:
term.coef.free = True
Run Fitting¶
In [21]:
Copied!
project.analysis.fit()
project.analysis.fit()
Standard fitting
📋 Using experiment 🔬 'xrd' for 'single' fitting
🚀 Starting fit process with 'lmfit (leastsq)'...
📈 Goodness-of-fit progress:
| iteration | time (s) | χ² | change / status | |
|---|---|---|---|---|
| 1 | 1 | 0.44 | 12.77 | |
| 2 | 21 | 5.25 | 4.44 | 65.2% ↓ |
| 3 | 39 | 9.29 | 3.82 | 13.9% ↓ |
| 4 | 57 | 13.31 | 3.43 | 10.4% ↓ |
| 5 | 79 | 18.46 | 3.41 | |
| 6 | 101 | 23.61 | 3.41 | |
| 7 | 123 | 28.70 | 3.40 | |
| 8 | 145 | 33.77 | 3.40 | |
| 9 | 167 | 38.91 | 3.40 | |
| 10 | 189 | 44.03 | 3.40 | |
| 11 | 190 | 44.30 | 3.40 |
🏆 Best goodness-of-fit (reduced χ²) is 3.40 at iteration 175
✅ Fitting complete.
In [22]:
Copied!
project.display.fit.results()
project.display.fit.results()
⚙️ Settings used:
| Name | Value | Description | |
|---|---|---|---|
| 1 | max_iterations | 1000 | Maximum solver iterations. |
📋 Least-squares fit results:
| Metric | Value | |
|---|---|---|
| 1 | 🧪 Minimizer | lmfit (leastsq) |
| 2 | ✅ Overall status | success |
| 3 | ⏱️ Fitting time (seconds) | 44.30 |
| 4 | 🔁 Iterations | 187 |
| 5 | 📏 Goodness-of-fit (reduced χ²) | 3.40 |
| 6 | 📏 R-factor (Rf, %) | 6.80 |
| 7 | 📏 R-factor squared (Rf², %) | 6.28 |
| 8 | 📏 Weighted R-factor (wR, %) | 5.32 |
📈 Refined parameters:
| datablock | category | entry | parameter | units | start | value | s.u. | change | |
|---|---|---|---|---|---|---|---|---|---|
| 1 | pbso4 | cell | length_a | Å | 8.4800 | 8.4810 | 0.0001 | 0.01 % ↑ | |
| 2 | pbso4 | cell | length_b | Å | 5.4000 | 5.3990 | 0.0000 | 0.02 % ↓ | |
| 3 | pbso4 | cell | length_c | Å | 6.9600 | 6.9605 | 0.0001 | 0.01 % ↑ | |
| 4 | xrd | linked_structure | pbso4 | scale | 0.0010 | 0.0010 | 0.0000 | 0.08 % ↓ | |
| 5 | xrd | peak | asym_beba_a0 | -0.2300 | -0.2188 | 0.0049 | 4.88 % ↓ | ||
| 6 | xrd | peak | asym_beba_b0 | -0.0300 | -0.0301 | 0.0010 | 0.27 % ↑ | ||
| 7 | xrd | peak | broad_gauss_u | deg² | 0.0300 | 0.0197 | 0.0009 | 34.43 % ↓ | |
| 8 | xrd | peak | broad_gauss_v | deg² | -0.0400 | -0.0185 | 0.0010 | 53.76 % ↓ | |
| 9 | xrd | peak | broad_gauss_w | deg² | 0.0100 | 0.0079 | 0.0003 | 21.24 % ↓ | |
| 10 | xrd | peak | broad_lorentz_y | deg | 0.0600 | 0.0645 | 0.0005 | 7.52 % ↑ | |
| 11 | xrd | instrument | twotheta_offset | deg | -0.0200 | -0.0289 | 0.0006 | 44.35 % ↑ | |
| 12 | xrd | background | 1 | coef | 149.0000 | 153.1702 | 0.4838 | 2.80 % ↑ | |
| 13 | xrd | background | 2 | coef | 67.0000 | 62.9284 | 0.7605 | 6.08 % ↓ | |
| 14 | xrd | background | 3 | coef | 9.0000 | 9.8579 | 0.6896 | 9.53 % ↑ | |
| 15 | xrd | background | 4 | coef | 10.0000 | 10.8109 | 0.6722 | 8.11 % ↑ | |
| 16 | xrd | background | 5 | coef | -5.0000 | -6.6118 | 0.6036 | 32.24 % ↑ | |
| 17 | xrd | background | 6 | coef | -9.0000 | -7.1219 | 0.5921 | 20.87 % ↓ |
• start = parameter value before refinement
• value = refined value from least-squares minimization
• s.u. = standard uncertainty (one sigma), from the covariance matrix
• change = relative change from start, in %; ↑ = increase, ↓ = decrease
• value = refined value from least-squares minimization
• s.u. = standard uncertainty (one sigma), from the covariance matrix
• change = relative change from start, in %; ↑ = increase, ↓ = decrease
Display Correlations¶
In [23]:
Copied!
project.display.fit.correlations()
project.display.fit.correlations()
Loading plot…
Display Pattern¶
In [24]:
Copied!
project.display.pattern(expt_name='xrd')
project.display.pattern(expt_name='xrd')
Loading plot…
In [25]:
Copied!
project.display.pattern(expt_name='xrd', x_min=77.6, x_max=82.2)
project.display.pattern(expt_name='xrd', x_min=77.6, x_max=82.2)
Loading plot…
💾 Save Project¶
In [26]:
Copied!
project.save_as(dir_path='projects/refine-pbso4-xray')
project.save_as(dir_path='projects/refine-pbso4-xray')
Saving project 📦 'pbso4_xray' to '../../../projects/refine-pbso4-xray'
├── 📄 project.edi
├── 📁 structures/
│ └── 📄 pbso4.edi
├── 📁 experiments/
│ └── 📄 xrd.edi
├── 📁 analysis/
│ └── 📄 analysis.edi
└── 📁 reports/
└── 📄 pbso4_xray.html