LiF — powder X-ray CW — polarization¶
Verifies the X-ray polarization correction on the same single-wavelength LiF reference.
Refinement: the overall scale only; all other parameters are taken from the FullProf reference.
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='lif')
structure.space_group.name_h_m = 'F m -3 m' # FullProf Space group symbol
structure.cell.length_a = 4.026700 # FullProf a
structure.atom_sites.create(
id='Li1', # FullProf Atom
type_symbol='Li', # 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=1.20000, # FullProf Biso
)
structure.atom_sites.create(
id='F1', # FullProf Atom
type_symbol='F', # 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.80000, # FullProf Biso
)
project.structures.add(structure)
structure = StructureFactory.from_scratch(name='lif')
structure.space_group.name_h_m = 'F m -3 m' # FullProf Space group symbol
structure.cell.length_a = 4.026700 # FullProf a
structure.atom_sites.create(
id='Li1', # FullProf Atom
type_symbol='Li', # 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=1.20000, # FullProf Biso
)
structure.atom_sites.create(
id='F1', # FullProf Atom
type_symbol='F', # 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.80000, # FullProf Biso
)
project.structures.add(structure)
Load the FullProf reference¶
In [5]:
Copied!
FULLPROF_PROJECT_DIR = 'pd-xray-cwl_lif'
FULLPROF_PRF_FILE = 'lif_single_polarized.prf'
FULLPROF_SUM_FILE = 'lif_single_polarized.sum'
FULLPROF_BAC_FILE = 'lif_single_polarized.bac'
FULLPROF_LABEL = verify.fullprof_label(FULLPROF_PROJECT_DIR, FULLPROF_SUM_FILE)
FULLPROF_ZERO = 0.0 # FullProf Zero
FULLPROF_SCALE = 0.01 # FullProf Scale
FULLPROF_WAVELENGTH = 1.540560 # FullProf Lambda1
FULLPROF_U = 0.048457 # FullProf U
FULLPROF_V = -0.083053 # FullProf V
FULLPROF_W = 0.040000 # FullProf W
FULLPROF_X = 0.0 # FullProf X
FULLPROF_Y = 0.049268 # FullProf Y
FULLPROF_WDT = 48.0 # FullProf Wdt
FULLPROF_POLARIZATION_COEFFICIENT = 0.5 # FullProf Rpolarz
FULLPROF_CTHM = 0.8 # FullProf Cthm
FULLPROF_MONOCHROMATOR_TWOTHETA = 26.5650511771 # acos(sqrt(Cthm)) in degrees
x, calc_fullprof = verify.load_fullprof_calc_profile(
FULLPROF_PROJECT_DIR,
FULLPROF_PRF_FILE,
FULLPROF_BAC_FILE,
FULLPROF_ZERO,
)
FULLPROF_PROJECT_DIR = 'pd-xray-cwl_lif'
FULLPROF_PRF_FILE = 'lif_single_polarized.prf'
FULLPROF_SUM_FILE = 'lif_single_polarized.sum'
FULLPROF_BAC_FILE = 'lif_single_polarized.bac'
FULLPROF_LABEL = verify.fullprof_label(FULLPROF_PROJECT_DIR, FULLPROF_SUM_FILE)
FULLPROF_ZERO = 0.0 # FullProf Zero
FULLPROF_SCALE = 0.01 # FullProf Scale
FULLPROF_WAVELENGTH = 1.540560 # FullProf Lambda1
FULLPROF_U = 0.048457 # FullProf U
FULLPROF_V = -0.083053 # FullProf V
FULLPROF_W = 0.040000 # FullProf W
FULLPROF_X = 0.0 # FullProf X
FULLPROF_Y = 0.049268 # FullProf Y
FULLPROF_WDT = 48.0 # FullProf Wdt
FULLPROF_POLARIZATION_COEFFICIENT = 0.5 # FullProf Rpolarz
FULLPROF_CTHM = 0.8 # FullProf Cthm
FULLPROF_MONOCHROMATOR_TWOTHETA = 26.5650511771 # acos(sqrt(Cthm)) in degrees
x, calc_fullprof = verify.load_fullprof_calc_profile(
FULLPROF_PROJECT_DIR,
FULLPROF_PRF_FILE,
FULLPROF_BAC_FILE,
FULLPROF_ZERO,
)
Create the experiment¶
In [6]:
Copied!
experiment = ExperimentFactory.from_scratch(
name='lif',
sample_form='powder',
beam_mode='constant wavelength',
radiation_probe='xray',
scattering_type='bragg',
)
verify.set_reference_as_measured(experiment, x, calc_fullprof)
experiment.linked_structures.create(structure_id='lif', scale=FULLPROF_SCALE)
experiment.instrument.setup_wavelength = FULLPROF_WAVELENGTH
experiment.instrument.calib_twotheta_offset = FULLPROF_ZERO
experiment.instrument.setup_polarization_coefficient = FULLPROF_POLARIZATION_COEFFICIENT
experiment.instrument.setup_monochromator_twotheta = FULLPROF_MONOCHROMATOR_TWOTHETA
experiment.peak.type = 'pseudo-voigt'
experiment.peak.broad_gauss_u = FULLPROF_U
experiment.peak.broad_gauss_v = FULLPROF_V
experiment.peak.broad_gauss_w = FULLPROF_W
experiment.peak.broad_lorentz_x = FULLPROF_X
experiment.peak.broad_lorentz_y = FULLPROF_Y
experiment.peak.cutoff_fwhm = FULLPROF_WDT
project.experiments.add(experiment)
experiment = ExperimentFactory.from_scratch(
name='lif',
sample_form='powder',
beam_mode='constant wavelength',
radiation_probe='xray',
scattering_type='bragg',
)
verify.set_reference_as_measured(experiment, x, calc_fullprof)
experiment.linked_structures.create(structure_id='lif', scale=FULLPROF_SCALE)
experiment.instrument.setup_wavelength = FULLPROF_WAVELENGTH
experiment.instrument.calib_twotheta_offset = FULLPROF_ZERO
experiment.instrument.setup_polarization_coefficient = FULLPROF_POLARIZATION_COEFFICIENT
experiment.instrument.setup_monochromator_twotheta = FULLPROF_MONOCHROMATOR_TWOTHETA
experiment.peak.type = 'pseudo-voigt'
experiment.peak.broad_gauss_u = FULLPROF_U
experiment.peak.broad_gauss_v = FULLPROF_V
experiment.peak.broad_gauss_w = FULLPROF_W
experiment.peak.broad_lorentz_x = FULLPROF_X
experiment.peak.broad_lorentz_y = FULLPROF_Y
experiment.peak.cutoff_fwhm = FULLPROF_WDT
project.experiments.add(experiment)
Peak profile type for experiment 'lif' changed to
pseudo-voigt
edi-cryspy VS FullProf¶
In [7]:
Copied!
experiment.calculator.type = 'cryspy'
project.analysis.calculate()
calc_ed_cryspy = experiment.data.intensity_calc
LABEL_ED_CRYSPY = verify.engine_label('cryspy')
project.display.pattern_comparison(
'lif',
reference=calc_fullprof,
candidate=calc_ed_cryspy,
reference_label=FULLPROF_LABEL,
candidate_label=LABEL_ED_CRYSPY,
)
experiment.calculator.type = 'cryspy'
project.analysis.calculate()
calc_ed_cryspy = experiment.data.intensity_calc
LABEL_ED_CRYSPY = verify.engine_label('cryspy')
project.display.pattern_comparison(
'lif',
reference=calc_fullprof,
candidate=calc_ed_cryspy,
reference_label=FULLPROF_LABEL,
candidate_label=LABEL_ED_CRYSPY,
)
Calculator for experiment 'lif' already set to
cryspy
Loading plot…
Fit edi-cryspy to FullProf¶
Cryspy applies polarization natively from the K and cthm values
passed by EasyDiffraction. FullProf's characteristic X-ray convention
for Rpolarz = 0.5 uses the same polarization expression multiplied
by two, so the seeded FullProf scale leaves the native Cryspy pattern
half as intense. This fit documents the convention mismatch by letting
the scale absorb that factor.
In [8]:
Copied!
experiment.linked_structures['lif'].scale.free = True
project.analysis.fit()
project.display.fit.results()
project.analysis.calculate()
calc_ed_cryspy_refined = experiment.data.intensity_calc
LABEL_ED_CRYSPY_REFINED = verify.engine_label('cryspy', note='refined')
project.display.pattern_comparison(
'lif',
reference=calc_fullprof,
candidate=calc_ed_cryspy_refined,
reference_label=FULLPROF_LABEL,
candidate_label=LABEL_ED_CRYSPY_REFINED,
)
experiment.linked_structures['lif'].scale.free = True
project.analysis.fit()
project.display.fit.results()
project.analysis.calculate()
calc_ed_cryspy_refined = experiment.data.intensity_calc
LABEL_ED_CRYSPY_REFINED = verify.engine_label('cryspy', note='refined')
project.display.pattern_comparison(
'lif',
reference=calc_fullprof,
candidate=calc_ed_cryspy_refined,
reference_label=FULLPROF_LABEL,
candidate_label=LABEL_ED_CRYSPY_REFINED,
)
Standard fitting
📋 Using experiment 🔬 'lif' for 'single' fitting
🚀 Starting fit process with 'lmfit (leastsq)'...
📈 Goodness-of-fit progress:
| iteration | time (s) | χ² | change / status | |
|---|---|---|---|---|
| 1 | 1 | 0.04 | 687.66 | |
| 2 | 5 | 0.16 | 0.02 | 100.0% ↓ |
| 3 | 8 | 0.29 | 0.02 |
🏆 Best goodness-of-fit (reduced χ²) is 0.02 at iteration 5
✅ 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.29 |
| 4 | 🔁 Iterations | 5 |
| 5 | 📏 Goodness-of-fit (reduced χ²) | 0.02 |
| 6 | 📏 R-factor (Rf, %) | 0.62 |
| 7 | 📏 R-factor squared (Rf², %) | 0.28 |
| 8 | 📏 Weighted R-factor (wR, %) | 0.28 |
📈 Refined parameters:
| datablock | category | entry | parameter | units | start | value | s.u. | change | |
|---|---|---|---|---|---|---|---|---|---|
| 1 | lif | linked_structure | lif | scale | 0.0100 | 0.0200 | 0.0000 | 99.90 % ↑ |
• 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
Loading plot…
Agreement check¶
In [9]:
Copied!
verify.assert_patterns_agree(
[
(
f'{LABEL_ED_CRYSPY} vs {FULLPROF_LABEL}',
calc_fullprof,
calc_ed_cryspy,
),
],
known_discrepancy=True,
reason=(
'Cryspy applies the native K/cthm polarization factor directly, '
'while FullProf multiplies the characteristic X-ray Rpolarz=0.5 '
'form by two; before scale refinement the Cryspy pattern is '
'therefore half as intense.'
),
)
verify.assert_patterns_agree([
(
f'{LABEL_ED_CRYSPY_REFINED} vs {FULLPROF_LABEL}',
calc_fullprof,
calc_ed_cryspy_refined,
),
])
verify.assert_patterns_agree(
[
(
f'{LABEL_ED_CRYSPY} vs {FULLPROF_LABEL}',
calc_fullprof,
calc_ed_cryspy,
),
],
known_discrepancy=True,
reason=(
'Cryspy applies the native K/cthm polarization factor directly, '
'while FullProf multiplies the characteristic X-ray Rpolarz=0.5 '
'form by two; before scale refinement the Cryspy pattern is '
'therefore half as intense.'
),
)
verify.assert_patterns_agree([
(
f'{LABEL_ED_CRYSPY_REFINED} vs {FULLPROF_LABEL}',
calc_fullprof,
calc_ed_cryspy_refined,
),
])
| Comparison | Metric | Expected | Actual | OK | |
|---|---|---|---|---|---|
| 1 | edi 0.20.1 (cryspy 0.13.0) vs FullProf 8.40 | Profile diff (%) | < 2.5 | 49.98 | ❌ |
| 2 | Max deviation (%) | < 6 | 49.99 | ❌ | |
| 3 | Area ratio | 0.99 to 1.01 | 0.5001 | ❌ | |
| 4 | Shape correlation | > 0.999 | 1.0000 | ✅ |
• Known discrepancy = Cryspy applies the native K/cthm polarization factor directly, while FullProf multiplies the characteristic X-ray Rpolarz=0.5 form by two; before scale refinement the Cryspy pattern is therefore half as intense.
| Comparison | Metric | Expected | Actual | OK | |
|---|---|---|---|---|---|
| 1 | edi 0.20.1 (cryspy 0.13.0, refined) vs FullProf 8.40 | Profile diff (%) | < 2.5 | 0.28 | ✅ |
| 2 | Max deviation (%) | < 6 | 0.20 | ✅ | |
| 3 | Area ratio | 0.99 to 1.01 | 0.9998 | ✅ | |
| 4 | Shape correlation | > 0.999 | 1.0000 | ✅ |
Out[9]:
True