Tb2Ti2O7 - single-crystal neutron CW - isotropic extinction¶
Verifies the cryspy isotropic extinction model against a FullProf single-crystal reference with isotropic ADPs.
Refinement: the overall scale and the extinction radius. Known difference: cryspy and FullProf use different extinction conventions.
In [2]:
Copied!
import easydiffraction as edi
from easydiffraction import ExperimentFactory
from easydiffraction import StructureFactory
from easydiffraction.analysis import verification as verify
import easydiffraction as edi
from easydiffraction import ExperimentFactory
from easydiffraction import StructureFactory
from easydiffraction.analysis import verification as verify
Build the project¶
In [3]:
Copied!
project = edi.Project()
project = edi.Project()
Define the structure¶
In [4]:
Copied!
structure = StructureFactory.from_scratch(name='tbti')
structure.space_group.name_h_m = 'F d -3 m' # FullProf Space group symbol
structure.cell.length_a = 10.130 # FullProf a
structure.atom_sites.create(
id='Tb', # FullProf Atom
type_symbol='Tb', # FullProf Typ
fract_x=0.5, # FullProf X
fract_y=0.5, # FullProf Y
fract_z=0.5, # FullProf Z
adp_type='Biso', # FullProf Biso
adp_iso=0.0, # FullProf Biso
)
structure.atom_sites.create(
id='Ti', # FullProf Atom
type_symbol='Ti', # FullProf Typ
fract_x=0.0, # FullProf X
fract_y=0.0, # FullProf Y
fract_z=0.0, # FullProf Z
adp_type='Biso', # FullProf Biso
adp_iso=0.0, # FullProf Biso
)
structure.atom_sites.create(
id='O1', # FullProf Atom
type_symbol='O', # FullProf Typ
fract_x=0.32804, # FullProf X
fract_y=0.125, # FullProf Y
fract_z=0.125, # FullProf Z
adp_type='Biso', # FullProf Biso
adp_iso=0.0, # FullProf Biso
)
structure.atom_sites.create(
id='O2', # FullProf Atom
type_symbol='O', # FullProf Typ
fract_x=0.375, # FullProf X
fract_y=0.375, # FullProf Y
fract_z=0.375, # FullProf Z
adp_type='Biso', # FullProf Biso
adp_iso=0.0, # FullProf Biso
)
project.structures.add(structure)
structure = StructureFactory.from_scratch(name='tbti')
structure.space_group.name_h_m = 'F d -3 m' # FullProf Space group symbol
structure.cell.length_a = 10.130 # FullProf a
structure.atom_sites.create(
id='Tb', # FullProf Atom
type_symbol='Tb', # FullProf Typ
fract_x=0.5, # FullProf X
fract_y=0.5, # FullProf Y
fract_z=0.5, # FullProf Z
adp_type='Biso', # FullProf Biso
adp_iso=0.0, # FullProf Biso
)
structure.atom_sites.create(
id='Ti', # FullProf Atom
type_symbol='Ti', # FullProf Typ
fract_x=0.0, # FullProf X
fract_y=0.0, # FullProf Y
fract_z=0.0, # FullProf Z
adp_type='Biso', # FullProf Biso
adp_iso=0.0, # FullProf Biso
)
structure.atom_sites.create(
id='O1', # FullProf Atom
type_symbol='O', # FullProf Typ
fract_x=0.32804, # FullProf X
fract_y=0.125, # FullProf Y
fract_z=0.125, # FullProf Z
adp_type='Biso', # FullProf Biso
adp_iso=0.0, # FullProf Biso
)
structure.atom_sites.create(
id='O2', # FullProf Atom
type_symbol='O', # FullProf Typ
fract_x=0.375, # FullProf X
fract_y=0.375, # FullProf Y
fract_z=0.375, # FullProf Z
adp_type='Biso', # FullProf Biso
adp_iso=0.0, # FullProf Biso
)
project.structures.add(structure)
In [5]:
Copied!
structure.show_as_text()
structure.show_as_text()
Structure 🧩 'tbti' as text
| Edi | |
|---|---|
| 1 | data_tbti |
| 2 | |
| 3 | _cell.length_a 10.13 |
| 4 | _cell.length_b 10.13 |
| 5 | _cell.length_c 10.13 |
| 6 | _cell.angle_alpha 90. |
| 7 | _cell.angle_beta 90. |
| 8 | _cell.angle_gamma 90. |
| 9 | |
| 10 | _space_group.name_h_m "F d -3 m" |
| 11 | _space_group.coord_system_code 2 |
| 12 | |
| 13 | _geom.min_bond_distance_cutoff 0. |
| 14 | _geom.bond_distance_inc 0.25 |
| 15 | |
| 16 | loop_ |
| 17 | _atom_site.id |
| 18 | _atom_site.type_symbol |
| 19 | _atom_site.fract_x |
| 20 | _atom_site.fract_y |
| 21 | _atom_site.fract_z |
| 22 | _atom_site.wyckoff_letter |
| 23 | _atom_site.multiplicity |
| 24 | _atom_site.occupancy |
| 25 | _atom_site.adp_iso |
| 26 | _atom_site.adp_type |
| 27 | Tb Tb 0.5 0.5 0.5 d 16 1. 0. Biso |
| 28 | Ti Ti 0. 0. 0. c 16 1. 0. Biso |
| 29 | O1 O 0.32804 0.125 0.125 f 48 1. 0. Biso |
| 30 | O2 O 0.375 0.375 0.375 b 8 1. 0. Biso |
Load the FullProf reference¶
In [6]:
Copied!
FULLPROF_PROJECT_DIR = 'sc-neut-cwl_tbti_isotropic-extinction'
FULLPROF_OUT_FILE = 'tbti.out'
FULLPROF_SCALE = 0.2609 # FullProf Scale
FULLPROF_WAVELENGTH = 0.7930 # FullProf Lambda
# cryspy uses Becker-Coppens isotropic extinction, not the one from
# FullProf.
EXTINCTION_RADIUS = 10.0
EXTINCTION_MOSAICITY = 35000.0
f2calc = verify.load_fullprof_sc_f2calc(FULLPROF_PROJECT_DIR, FULLPROF_OUT_FILE)
FULLPROF_LABEL = verify.fullprof_label(FULLPROF_PROJECT_DIR, FULLPROF_OUT_FILE)
FULLPROF_PROJECT_DIR = 'sc-neut-cwl_tbti_isotropic-extinction'
FULLPROF_OUT_FILE = 'tbti.out'
FULLPROF_SCALE = 0.2609 # FullProf Scale
FULLPROF_WAVELENGTH = 0.7930 # FullProf Lambda
# cryspy uses Becker-Coppens isotropic extinction, not the one from
# FullProf.
EXTINCTION_RADIUS = 10.0
EXTINCTION_MOSAICITY = 35000.0
f2calc = verify.load_fullprof_sc_f2calc(FULLPROF_PROJECT_DIR, FULLPROF_OUT_FILE)
FULLPROF_LABEL = verify.fullprof_label(FULLPROF_PROJECT_DIR, FULLPROF_OUT_FILE)
Create the experiment¶
In [7]:
Copied!
experiment = ExperimentFactory.from_scratch(
name='tbti',
sample_form='single crystal',
beam_mode='constant wavelength',
radiation_probe='neutron',
scattering_type='bragg',
)
experiment.linked_structure.structure_id = 'tbti'
experiment.linked_structure.scale = FULLPROF_SCALE
experiment.instrument.setup_wavelength = FULLPROF_WAVELENGTH
experiment.extinction.type = 'becker-coppens'
experiment.extinction.model = 'gauss'
experiment.extinction.radius.value = EXTINCTION_RADIUS
experiment.extinction.mosaicity.value = EXTINCTION_MOSAICITY
verify.set_reference_reflections(experiment, f2calc)
project.experiments.add(experiment)
experiment = ExperimentFactory.from_scratch(
name='tbti',
sample_form='single crystal',
beam_mode='constant wavelength',
radiation_probe='neutron',
scattering_type='bragg',
)
experiment.linked_structure.structure_id = 'tbti'
experiment.linked_structure.scale = FULLPROF_SCALE
experiment.instrument.setup_wavelength = FULLPROF_WAVELENGTH
experiment.extinction.type = 'becker-coppens'
experiment.extinction.model = 'gauss'
experiment.extinction.radius.value = EXTINCTION_RADIUS
experiment.extinction.mosaicity.value = EXTINCTION_MOSAICITY
verify.set_reference_reflections(experiment, f2calc)
project.experiments.add(experiment)
Extinction type changed to
becker-coppens
edi-cryspy VS FullProf¶
In [8]:
Copied!
calc_ed_cryspy = verify.calculate_reflections(project, experiment, 'cryspy')
LABEL_ED_CRYSPY = verify.engine_label('cryspy')
reference, candidate = verify.align_reflections(f2calc, calc_ed_cryspy)
project.display.reflection_comparison(
'tbti',
reference=reference,
candidate=candidate,
reference_label=FULLPROF_LABEL,
candidate_label=LABEL_ED_CRYSPY,
)
calc_ed_cryspy = verify.calculate_reflections(project, experiment, 'cryspy')
LABEL_ED_CRYSPY = verify.engine_label('cryspy')
reference, candidate = verify.align_reflections(f2calc, calc_ed_cryspy)
project.display.reflection_comparison(
'tbti',
reference=reference,
candidate=candidate,
reference_label=FULLPROF_LABEL,
candidate_label=LABEL_ED_CRYSPY,
)
Calculator for experiment 'tbti' already set to
cryspy
Loading plot…
Fit edi-cryspy to FullProf¶
In [9]:
Copied!
experiment.calculator.type = 'cryspy'
experiment.linked_structure.scale.free = True
experiment.extinction.radius.free = True
project.analysis.fit()
project.display.fit.results()
calc_ed_cryspy_refined = verify.calculate_reflections(project, experiment, 'cryspy')
LABEL_ED_CRYSPY_REFINED = verify.engine_label('cryspy', note='scale + ext radius')
reference_refined, candidate_refined = verify.align_reflections(f2calc, calc_ed_cryspy_refined)
project.display.reflection_comparison(
'tbti',
reference=reference_refined,
candidate=candidate_refined,
reference_label=FULLPROF_LABEL,
candidate_label=LABEL_ED_CRYSPY_REFINED,
)
verify.report_refinement_closeness(
reference,
candidate,
candidate_refined,
)
experiment.calculator.type = 'cryspy'
experiment.linked_structure.scale.free = True
experiment.extinction.radius.free = True
project.analysis.fit()
project.display.fit.results()
calc_ed_cryspy_refined = verify.calculate_reflections(project, experiment, 'cryspy')
LABEL_ED_CRYSPY_REFINED = verify.engine_label('cryspy', note='scale + ext radius')
reference_refined, candidate_refined = verify.align_reflections(f2calc, calc_ed_cryspy_refined)
project.display.reflection_comparison(
'tbti',
reference=reference_refined,
candidate=candidate_refined,
reference_label=FULLPROF_LABEL,
candidate_label=LABEL_ED_CRYSPY_REFINED,
)
verify.report_refinement_closeness(
reference,
candidate,
candidate_refined,
)
Calculator for experiment 'tbti' already set to
cryspy
Standard fitting
📋 Using experiment 🔬 'tbti' for 'single' fitting
🚀 Starting fit process with 'lmfit (leastsq)'...
📈 Goodness-of-fit progress:
| iteration | time (s) | χ² | change / status | |
|---|---|---|---|---|
| 1 | 1 | 0.01 | 559733.19 | |
| 2 | 6 | 0.06 | 227010.05 | 59.4% ↓ |
| 3 | 10 | 0.10 | 123452.76 | 45.6% ↓ |
| 4 | 13 | 0.14 | 4334.85 | 96.5% ↓ |
| 5 | 16 | 0.16 | 1422.02 | 67.2% ↓ |
| 6 | 26 | 0.25 | 1416.64 |
🏆 Best goodness-of-fit (reduced χ²) is 1416.64 at iteration 25
✅ Fitting complete.
⚙️ 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) | 0.25 |
| 4 | 🔁 Iterations | 23 |
| 5 | 📏 Goodness-of-fit (reduced χ²) | 1416.64 |
| 6 | 📏 R-factor (Rf, %) | 2.52 |
| 7 | 📏 R-factor squared (Rf², %) | 4.34 |
| 8 | 📏 Weighted R-factor (wR, %) | 4.34 |
📈 Refined parameters:
• 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
Calculator for experiment 'tbti' already set to
cryspy
Loading plot…
| Metric | Before | After | |
|---|---|---|---|
| 1 | Profile diff (%) | 86.18 | 4.34 |
| 2 | Max deviation (%) | 85.73 | 9.92 |
| 3 | Area ratio | 0.1351 | 0.9985 |
| 4 | Shape correlation | 0.9982 | 0.9985 |
Agreement check¶
In [10]:
Copied!
verify.assert_patterns_agree(
[
(f'{LABEL_ED_CRYSPY_REFINED} vs {FULLPROF_LABEL}', reference_refined, candidate_refined),
],
known_discrepancy=True,
reason='cryspy and FullProf use different extinction conventions.',
)
verify.assert_patterns_agree(
[
(f'{LABEL_ED_CRYSPY_REFINED} vs {FULLPROF_LABEL}', reference_refined, candidate_refined),
],
known_discrepancy=True,
reason='cryspy and FullProf use different extinction conventions.',
)
| Comparison | Metric | Expected | Actual | OK | |
|---|---|---|---|---|---|
| 1 | edi 0.19.0 (cryspy 0.12.1, scale + ext radius) vs FullProf 8.40 | Profile diff (%) | < 2.5 | 4.34 | ❌ |
| 2 | Max deviation (%) | < 6 | 9.92 | ❌ | |
| 3 | Area ratio | 0.99 to 1.01 | 0.9985 | ✅ | |
| 4 | Shape correlation | > 0.999 | 0.9985 | ❌ |
• Known discrepancy = cryspy and FullProf use different extinction conventions.
Out[10]:
True