Structure Refinement: Co2SiO4, D20 (T-scan)¶
This example demonstrates a Rietveld refinement of the Co2SiO4 crystal structure using constant-wavelength neutron powder diffraction data from D20 at ILL. A sequential refinement is performed against a temperature scan using sequential fitting, which processes each data file independently without loading all datasets into memory at once.
🛠️ Import Library¶
import easydiffraction as ed
📦 Define Project¶
The project object manages structures, experiments, analysis, display, and other related components.
project = ed.Project(name='cosio_d20_scan')
analysis = project.analysis
display = project.display
The project must be saved before running sequential fitting, so that
results can be written to analysis/results.csv.
project.save_as(dir_path='projects/ed_17_cosio_d20_scan')
Saving project 📦 'cosio_d20_scan' to '../../../projects/ed_17_cosio_d20_scan'
├── 📄 project.cif
├── 📁 structures/
├── 📁 experiments/
├── 📁 analysis/
│ └── 📄 analysis.cif
└── 📁 reports/
└── 📄 cosio_d20_scan.html
project.structures.create(name='cosio')
struct = project.structures['cosio']
Set Space Group¶
struct.space_group.name_h_m = 'P n m a'
struct.space_group.it_coordinate_system_code = 'abc'
Set Unit Cell¶
struct.cell.length_a = 10.31
struct.cell.length_b = 6.0
struct.cell.length_c = 4.79
Set Atom Sites¶
struct.atom_sites.create(
label='Co1',
type_symbol='Co',
fract_x=0,
fract_y=0,
fract_z=0,
adp_iso=0.3,
)
struct.atom_sites.create(
label='Co2',
type_symbol='Co',
fract_x=0.279,
fract_y=0.25,
fract_z=0.985,
adp_iso=0.3,
)
struct.atom_sites.create(
label='Si',
type_symbol='Si',
fract_x=0.094,
fract_y=0.25,
fract_z=0.429,
adp_iso=0.34,
)
struct.atom_sites.create(
label='O1',
type_symbol='O',
fract_x=0.091,
fract_y=0.25,
fract_z=0.771,
adp_iso=0.63,
)
struct.atom_sites.create(
label='O2',
type_symbol='O',
fract_x=0.448,
fract_y=0.25,
fract_z=0.217,
adp_iso=0.59,
)
struct.atom_sites.create(
label='O3',
type_symbol='O',
fract_x=0.164,
fract_y=0.032,
fract_z=0.28,
adp_iso=0.83,
)
Display Structure¶
project.structure_style.atom_view = 'adp'
project.display.structure(struct_name='cosio')
Structure 🧩 'cosio' (Atom view type: 'adp')
wheel = zoom
right-drag = pan
zip_path = ed.download_data(id=25, destination='data')
Getting data...
Data #25: Co2SiO4, D20 (ILL), 3 files: ~50K, ~300K, ~500K
✅ Data #25 downloaded to '../../../data/ed-25.zip'
Extract Data Files¶
scan_data_dir = 'experiments/d20_scan'
data_paths = ed.extract_data_paths_from_zip(
zip_path,
destination=project.info.path / scan_data_dir,
)
Create Template Experiment¶
project.experiments.add_from_data_path(
name='d20',
data_path=data_paths[0],
)
expt = project.experiments['d20']
Data loaded successfully
Experiment 🔬 'd20'. Number of data points: 1507.
Set Instrument¶
expt.instrument.setup_wavelength = 1.87
expt.instrument.calib_twotheta_offset = 0.29
Set Peak Profile¶
expt.peak.broad_gauss_u = 0.24
expt.peak.broad_gauss_v = -0.53
expt.peak.broad_gauss_w = 0.38
expt.peak.broad_lorentz_y = 0.02
Set Excluded Regions¶
expt.excluded_regions.create(id='1', start=0, end=8)
expt.excluded_regions.create(id='2', start=150, end=180)
Set Background¶
expt.background.create(id='1', x=8, y=609)
expt.background.create(id='2', x=9, y=581)
expt.background.create(id='3', x=10, y=563)
expt.background.create(id='4', x=11, y=540)
expt.background.create(id='5', x=12, y=520)
expt.background.create(id='6', x=15, y=507)
expt.background.create(id='7', x=25, y=463)
expt.background.create(id='8', x=30, y=434)
expt.background.create(id='9', x=50, y=451)
expt.background.create(id='10', x=70, y=431)
expt.background.create(id='11', x=90, y=414)
expt.background.create(id='12', x=110, y=361)
expt.background.create(id='13', x=130, y=292)
expt.background.create(id='14', x=150, y=241)
Set Linked Phases¶
expt.linked_phases.create(id='cosio', scale=1.2)
🚀 Perform Analysis¶
This section shows how to set free parameters, define constraints, and run the sequential refinement.
Set Free Parameters¶
struct.cell.length_a.free = True
struct.cell.length_b.free = True
struct.cell.length_c.free = True
struct.atom_sites['Co2'].fract_x.free = True
struct.atom_sites['Co2'].fract_z.free = True
struct.atom_sites['Si'].fract_x.free = True
struct.atom_sites['Si'].fract_z.free = True
struct.atom_sites['O1'].fract_x.free = True
struct.atom_sites['O1'].fract_z.free = True
struct.atom_sites['O2'].fract_x.free = True
struct.atom_sites['O2'].fract_z.free = True
struct.atom_sites['O3'].fract_x.free = True
struct.atom_sites['O3'].fract_y.free = True
struct.atom_sites['O3'].fract_z.free = True
struct.atom_sites['Co1'].adp_iso.free = True
struct.atom_sites['Co2'].adp_iso.free = True
struct.atom_sites['Si'].adp_iso.free = True
struct.atom_sites['O1'].adp_iso.free = True
struct.atom_sites['O2'].adp_iso.free = True
struct.atom_sites['O3'].adp_iso.free = True
expt.linked_phases['cosio'].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
for point in expt.background:
point.y.free = True
Set Constraints¶
Set aliases for parameters.
analysis.aliases.create(
label='biso_Co1',
param=struct.atom_sites['Co1'].adp_iso,
)
analysis.aliases.create(
label='biso_Co2',
param=struct.atom_sites['Co2'].adp_iso,
)
Set constraints.
analysis.constraints.create(expression='biso_Co2 = biso_Co1')
Set Minimizer¶
analysis.minimizer.type = 'bumps (lm)'
Current minimizer changed to
bumps (lm)
Run Fitting¶
This is the fitting of the first dataset to optimize the initial parameters for the sequential fitting. This step is optional but can help with convergence and speed of the sequential fitting, especially if the initial parameters are far from optimal.
analysis.fit()
Standard fitting
📋 Using experiment 🔬 'd20' for 'single' fitting
🚀 Starting fit process with 'bumps (lm)'...
📈 Goodness-of-fit progress:
| iteration | time (s) | χ² | change / status | |
|---|---|---|---|---|
| 1 | 1 | 0.39 | 12.79 | |
| 2 | 41 | 6.81 | 5.12 | 60.0% ↓ |
| 3 | 81 | 12.48 | 4.82 | 5.8% ↓ |
| 4 | 121 | 18.34 | 4.82 | |
| 5 | 161 | 24.52 | 4.82 | |
| 6 | 201 | 30.10 | 4.82 | |
| 7 | 241 | 35.63 | 4.82 | |
| 8 | 283 | 47.99 | 4.82 |
🏆 Best goodness-of-fit (reduced χ²) is 4.82 at iteration 283
✅ Fitting complete.
Saving project 📦 'cosio_d20_scan' to '../../../projects/ed_17_cosio_d20_scan'
├── 📄 project.cif
├── 📁 structures/
│ └── 📄 cosio.cif
├── 📁 experiments/
│ └── 📄 d20.cif
├── 📁 analysis/
│ └── 📄 analysis.cif
└── 📁 reports/
└── 📄 cosio_d20_scan.html
display.fit.results()
⚙️ Settings used:
| Name | Value | Description | |
|---|---|---|---|
| 1 | max_iterations | 1000 | Maximum solver iterations. |
📋 Least-squares fit results:
| Metric | Value | |
|---|---|---|
| 1 | 🧪 Minimizer | bumps (lm) |
| 2 | ✅ Overall status | success |
| 3 | ⏱️ Fitting time (seconds) | 47.99 |
| 4 | 📏 Goodness-of-fit (reduced χ²) | 4.82 |
| 5 | 📏 R-factor (Rf, %) | 3.16 |
| 6 | 📏 R-factor squared (Rf², %) | 4.68 |
| 7 | 📏 Weighted R-factor (wR, %) | 5.02 |
📈 Refined parameters:
| datablock | category | entry | parameter | units | start | value | s.u. | change | |
|---|---|---|---|---|---|---|---|---|---|
| 1 | cosio | cell | length_a | Å | 10.3100 | 10.3071 | 0.0003 | 0.03 % ↓ | |
| 2 | cosio | cell | length_b | Å | 6.0000 | 6.0030 | 0.0002 | 0.05 % ↑ | |
| 3 | cosio | cell | length_c | Å | 4.7900 | 4.7865 | 0.0001 | 0.07 % ↓ | |
| 4 | cosio | atom_site | Co1 | adp_iso | Ų | 0.3000 | 0.1626 | 0.0807 | 45.79 % ↓ |
| 5 | cosio | atom_site | Co2 | fract_x | 0.2790 | 0.2784 | 0.0007 | 0.22 % ↓ | |
| 6 | cosio | atom_site | Co2 | fract_z | 0.9850 | 0.9809 | 0.0015 | 0.41 % ↓ | |
| 7 | cosio | atom_site | Si | fract_x | 0.0940 | 0.0934 | 0.0004 | 0.61 % ↓ | |
| 8 | cosio | atom_site | Si | fract_z | 0.4290 | 0.4285 | 0.0009 | 0.13 % ↓ | |
| 9 | cosio | atom_site | Si | adp_iso | Ų | 0.3400 | 0.3852 | 0.0650 | 13.29 % ↑ |
| 10 | cosio | atom_site | O1 | fract_x | 0.0910 | 0.0907 | 0.0003 | 0.38 % ↓ | |
| 11 | cosio | atom_site | O1 | fract_z | 0.7710 | 0.7718 | 0.0007 | 0.10 % ↑ | |
| 12 | cosio | atom_site | O1 | adp_iso | Ų | 0.6300 | 0.6489 | 0.0601 | 3.00 % ↑ |
| 13 | cosio | atom_site | O2 | fract_x | 0.4480 | 0.4484 | 0.0003 | 0.08 % ↑ | |
| 14 | cosio | atom_site | O2 | fract_z | 0.2170 | 0.2166 | 0.0007 | 0.19 % ↓ | |
| 15 | cosio | atom_site | O2 | adp_iso | Ų | 0.5900 | 0.4776 | 0.0596 | 19.06 % ↓ |
| 16 | cosio | atom_site | O3 | fract_x | 0.1640 | 0.1633 | 0.0002 | 0.44 % ↓ | |
| 17 | cosio | atom_site | O3 | fract_y | 0.0320 | 0.0311 | 0.0003 | 2.81 % ↓ | |
| 18 | cosio | atom_site | O3 | fract_z | 0.2800 | 0.2796 | 0.0005 | 0.15 % ↓ | |
| 19 | cosio | atom_site | O3 | adp_iso | Ų | 0.8300 | 0.8579 | 0.0503 | 3.36 % ↑ |
| 20 | d20 | linked_phases | cosio | scale | 1.2000 | 1.1989 | 0.0109 | 0.09 % ↓ | |
| 21 | d20 | peak | broad_gauss_u | deg² | 0.2400 | 0.2434 | 0.0071 | 1.40 % ↑ | |
| 22 | d20 | peak | broad_gauss_v | deg² | -0.5300 | -0.5314 | 0.0152 | 0.27 % ↑ | |
| 23 | d20 | peak | broad_gauss_w | deg² | 0.3800 | 0.3829 | 0.0094 | 0.77 % ↑ | |
| 24 | d20 | peak | broad_lorentz_y | deg | 0.0200 | 0.0213 | 0.0046 | 6.32 % ↑ | |
| 25 | d20 | instrument | twotheta_offset | deg | 0.2900 | 0.2870 | 0.0021 | 1.03 % ↓ | |
| 26 | d20 | background | 1 | y | 609.0000 | 609.9949 | 15.1049 | 0.16 % ↑ | |
| 27 | d20 | background | 2 | y | 581.0000 | 604.4402 | 10.2222 | 4.03 % ↑ | |
| 28 | d20 | background | 3 | y | 563.0000 | 591.7779 | 9.7540 | 5.11 % ↑ | |
| 29 | d20 | background | 4 | y | 540.0000 | 576.3858 | 9.2903 | 6.74 % ↑ | |
| 30 | d20 | background | 5 | y | 520.0000 | 527.7100 | 6.2817 | 1.48 % ↑ | |
| 31 | d20 | background | 6 | y | 507.0000 | 505.0536 | 3.5808 | 0.38 % ↓ | |
| 32 | d20 | background | 7 | y | 463.0000 | 484.1902 | 3.3227 | 4.58 % ↑ | |
| 33 | d20 | background | 8 | y | 434.0000 | 419.1261 | 2.4478 | 3.43 % ↓ | |
| 34 | d20 | background | 9 | y | 451.0000 | 456.6411 | 2.3229 | 1.25 % ↑ | |
| 35 | d20 | background | 10 | y | 431.0000 | 424.4090 | 2.0812 | 1.53 % ↓ | |
| 36 | d20 | background | 11 | y | 414.0000 | 413.1382 | 2.3243 | 0.21 % ↓ | |
| 37 | d20 | background | 12 | y | 361.0000 | 357.9666 | 2.1613 | 0.84 % ↓ | |
| 38 | d20 | background | 13 | y | 292.0000 | 286.6474 | 2.0252 | 1.83 % ↓ | |
| 39 | d20 | background | 14 | y | 241.0000 | 238.5437 | 3.0950 | 1.02 % ↓ |
• 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¶
display.fit.correlations()
Display Pattern¶
display.pattern(expt_name='d20')
Display Structure¶
project.structure_style.atom_view = 'adp'
project.display.structure(struct_name='cosio')
Structure 🧩 'cosio' (Atom view type: 'adp')
wheel = zoom
right-drag = pan
Run Sequential Fitting¶
Set output verbosity level to "short" to show only one-line status messages during the analysis process.
project.verbosity = 'short'
Create a persisted extract rule that reads the temperature from each data file.
temperature = 'diffrn.ambient_temperature'
analysis.sequential_fit_extract.create(
id='temperature',
target=temperature,
pattern=r'^TEMP\s+([0-9.]+)',
required=True,
)
Set the sequential fitting parameters.
analysis.fitting_mode.type = 'sequential'
analysis.sequential_fit.data_dir = scan_data_dir
analysis.sequential_fit.max_workers = 'auto'
analysis.sequential_fit.reverse = True
Fitting mode changed to
sequential
Run the sequential fit over all data files in the scan directory.
analysis.fit()
Sequential fitting
🚀 Starting fit process with 'bumps (lm)'...
📋 3 files in 1 chunks (max_workers=4)
📈 Goodness-of-fit progress:
| chunk | progress | time (s) | files | count | average χ² | status | |
|---|---|---|---|---|---|---|---|
| 1 | 1/1 | 100.0% | 146.09 | all594842.dat - all594687.dat | 3 | 4.37 | ✅ |
✅ Sequential fitting complete: 3 files processed.
📄 Results saved to '../../../projects/ed_17_cosio_d20_scan/analysis/results.csv'
Saving project 📦 'cosio_d20_scan' to '../../../projects/ed_17_cosio_d20_scan'
├── 📄 project.cif
├── 📁 structures/
│ └── 📄 cosio.cif
├── 📁 experiments/
│ └── 📄 d20.cif
├── 📁 analysis/
│ ├── 📄 analysis.cif
│ └── 📄 results.csv
└── 📁 reports/
└── 📄 cosio_d20_scan.html
Replay a Dataset¶
Apply fitted parameters from the first CSV row and plot the result.
project.apply_params_from_csv(row_index=0)
display.pattern(expt_name='d20')
Apply fitted parameters from the last CSV row and plot the result.
project.apply_params_from_csv(row_index=-1)
display.pattern(expt_name='d20')
Display Parameter Evolution¶
Reuse the extracted diffrn path as the x-axis in the following plots.
Plot fit quality metrics vs. temperature.
display.fit.series(analysis.fit_result.success, versus=temperature)
display.fit.series(analysis.fit_result.reduced_chi_square, versus=temperature)
display.fit.series(analysis.fit_result.iterations, versus=temperature)
Plot unit cell parameters vs. temperature.
display.fit.series(struct.cell.length_a, versus=temperature)
display.fit.series(struct.cell.length_b, versus=temperature)
display.fit.series(struct.cell.length_c, versus=temperature)
Plot isotropic displacement parameters vs. temperature.
display.fit.series(struct.atom_sites['Co1'].adp_iso, versus=temperature)
display.fit.series(struct.atom_sites['Si'].adp_iso, versus=temperature)
display.fit.series(struct.atom_sites['O1'].adp_iso, versus=temperature)
display.fit.series(struct.atom_sites['O2'].adp_iso, versus=temperature)
display.fit.series(struct.atom_sites['O3'].adp_iso, versus=temperature)
Plot selected fractional coordinates vs. temperature.
display.fit.series(struct.atom_sites['Co2'].fract_x, versus=temperature)
display.fit.series(struct.atom_sites['Co2'].fract_z, versus=temperature)
display.fit.series(struct.atom_sites['O1'].fract_z, versus=temperature)
display.fit.series(struct.atom_sites['O2'].fract_z, versus=temperature)
display.fit.series(struct.atom_sites['O3'].fract_z, versus=temperature)