PbSO₄ — powder X-ray CW — round robin¶
Verifies the anglesite X-ray round-robin case with the Cu Kα doublet and FullProf empirical asymmetry.
Refinement: atomic ADPs, the overall scale, and Bérar-Baldinozzi coefficients (round-robin case); see the known difference below.
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='pbso4')
structure.space_group.name_h_m = 'P n m a' # FullProf Space group symbol
structure.cell.length_a = 8.485900 # FullProf a
structure.cell.length_b = 5.402259 # FullProf b
structure.cell.length_c = 6.964587 # FullProf c
structure.atom_sites.create(
id='Pb', # FullProf Atom
type_symbol='Pb', # FullProf Typ
fract_x=0.18822, # FullProf X
fract_y=0.25, # FullProf Y
fract_z=0.16711, # FullProf Z
adp_type='Biso', # FullProf Biso
adp_iso=1.86290, # FullProf Biso
)
structure.atom_sites.create(
id='S', # FullProf Atom
type_symbol='S', # FullProf Typ
fract_x=0.06306, # FullProf X
fract_y=0.25, # FullProf Y
fract_z=0.68485, # FullProf Z
adp_type='Biso', # FullProf Biso
adp_iso=1.34971, # FullProf Biso
)
structure.atom_sites.create(
id='O1', # FullProf Atom
type_symbol='O', # FullProf Typ
fract_x=0.90281, # FullProf X
fract_y=0.25, # FullProf Y
fract_z=0.59724, # FullProf Z
adp_type='Biso', # FullProf Biso
adp_iso=1.19713, # FullProf Biso
)
structure.atom_sites.create(
id='O2', # FullProf Atom
type_symbol='O', # FullProf Typ
fract_x=0.18443, # FullProf X
fract_y=0.25, # FullProf Y
fract_z=0.54586, # FullProf Z
adp_type='Biso', # FullProf Biso
adp_iso=2.56174, # FullProf Biso
)
structure.atom_sites.create(
id='O3', # FullProf Atom
type_symbol='O', # FullProf Typ
fract_x=0.08094, # FullProf X
fract_y=0.02239, # FullProf Y
fract_z=0.81289, # FullProf Z
adp_type='Biso', # FullProf Biso
adp_iso=1.37463, # FullProf Biso
)
project.structures.add(structure)
structure = StructureFactory.from_scratch(name='pbso4')
structure.space_group.name_h_m = 'P n m a' # FullProf Space group symbol
structure.cell.length_a = 8.485900 # FullProf a
structure.cell.length_b = 5.402259 # FullProf b
structure.cell.length_c = 6.964587 # FullProf c
structure.atom_sites.create(
id='Pb', # FullProf Atom
type_symbol='Pb', # FullProf Typ
fract_x=0.18822, # FullProf X
fract_y=0.25, # FullProf Y
fract_z=0.16711, # FullProf Z
adp_type='Biso', # FullProf Biso
adp_iso=1.86290, # FullProf Biso
)
structure.atom_sites.create(
id='S', # FullProf Atom
type_symbol='S', # FullProf Typ
fract_x=0.06306, # FullProf X
fract_y=0.25, # FullProf Y
fract_z=0.68485, # FullProf Z
adp_type='Biso', # FullProf Biso
adp_iso=1.34971, # FullProf Biso
)
structure.atom_sites.create(
id='O1', # FullProf Atom
type_symbol='O', # FullProf Typ
fract_x=0.90281, # FullProf X
fract_y=0.25, # FullProf Y
fract_z=0.59724, # FullProf Z
adp_type='Biso', # FullProf Biso
adp_iso=1.19713, # FullProf Biso
)
structure.atom_sites.create(
id='O2', # FullProf Atom
type_symbol='O', # FullProf Typ
fract_x=0.18443, # FullProf X
fract_y=0.25, # FullProf Y
fract_z=0.54586, # FullProf Z
adp_type='Biso', # FullProf Biso
adp_iso=2.56174, # FullProf Biso
)
structure.atom_sites.create(
id='O3', # FullProf Atom
type_symbol='O', # FullProf Typ
fract_x=0.08094, # FullProf X
fract_y=0.02239, # FullProf Y
fract_z=0.81289, # FullProf Z
adp_type='Biso', # FullProf Biso
adp_iso=1.37463, # FullProf Biso
)
project.structures.add(structure)
Load the FullProf reference¶
In [5]:
Copied!
FULLPROF_PROJECT_DIR = 'pd-xray-cwl_pbso4_round-robin'
FULLPROF_PRF_FILE = 'pbsox.prf'
FULLPROF_SUM_FILE = 'pbsox.sum'
FULLPROF_BAC_FILE = 'pbsox.bac'
FULLPROF_LABEL = verify.fullprof_label(FULLPROF_PROJECT_DIR, FULLPROF_SUM_FILE)
FULLPROF_ZERO = 0.00363 # FullProf Zero
FULLPROF_SCALE = 0.0004703142 # FullProf Scale
FULLPROF_WAVELENGTH_1 = 1.540560 # FullProf Lambda1
FULLPROF_WAVELENGTH_2 = 1.544400 # FullProf Lambda2
FULLPROF_WAVELENGTH_2_TO_1_RATIO = 0.50000 # FullProf Ratio
FULLPROF_U = 0.048457 # FullProf U
FULLPROF_V = -0.083053 # FullProf V
FULLPROF_W = 0.035188 # FullProf W
FULLPROF_X = 0.0 # FullProf X
FULLPROF_Y = 0.058360 # FullProf Y
FULLPROF_WDT = 48.0 # FullProf Wdt
FULLPROF_ASY_1 = -0.41356 # FullProf Asy1
FULLPROF_ASY_2 = 0.0 # FullProf Asy2
FULLPROF_ASY_3 = 1.26777 # FullProf Asy3
FULLPROF_ASY_4 = 0.0 # FullProf Asy4
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_pbso4_round-robin'
FULLPROF_PRF_FILE = 'pbsox.prf'
FULLPROF_SUM_FILE = 'pbsox.sum'
FULLPROF_BAC_FILE = 'pbsox.bac'
FULLPROF_LABEL = verify.fullprof_label(FULLPROF_PROJECT_DIR, FULLPROF_SUM_FILE)
FULLPROF_ZERO = 0.00363 # FullProf Zero
FULLPROF_SCALE = 0.0004703142 # FullProf Scale
FULLPROF_WAVELENGTH_1 = 1.540560 # FullProf Lambda1
FULLPROF_WAVELENGTH_2 = 1.544400 # FullProf Lambda2
FULLPROF_WAVELENGTH_2_TO_1_RATIO = 0.50000 # FullProf Ratio
FULLPROF_U = 0.048457 # FullProf U
FULLPROF_V = -0.083053 # FullProf V
FULLPROF_W = 0.035188 # FullProf W
FULLPROF_X = 0.0 # FullProf X
FULLPROF_Y = 0.058360 # FullProf Y
FULLPROF_WDT = 48.0 # FullProf Wdt
FULLPROF_ASY_1 = -0.41356 # FullProf Asy1
FULLPROF_ASY_2 = 0.0 # FullProf Asy2
FULLPROF_ASY_3 = 1.26777 # FullProf Asy3
FULLPROF_ASY_4 = 0.0 # FullProf Asy4
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='pbso4',
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='pbso4', scale=FULLPROF_SCALE)
experiment.instrument.setup_wavelength = FULLPROF_WAVELENGTH_1
experiment.instrument.setup_wavelength_2 = FULLPROF_WAVELENGTH_2
experiment.instrument.setup_wavelength_2_to_1_ratio = FULLPROF_WAVELENGTH_2_TO_1_RATIO
experiment.instrument.calib_twotheta_offset = FULLPROF_ZERO
experiment.peak.type = 'pseudo-voigt + berar-baldinozzi asymmetry'
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.asym_beba_a0 = FULLPROF_ASY_1
experiment.peak.asym_beba_b0 = FULLPROF_ASY_2
experiment.peak.asym_beba_a1 = FULLPROF_ASY_3
experiment.peak.asym_beba_b1 = FULLPROF_ASY_4
# FullProf excludes 0-10 deg and 154-180 deg in the PCR.
experiment.excluded_regions.create(id='1', start=0.0, end=10.0)
experiment.excluded_regions.create(id='2', start=154.0, end=180.0)
experiment.peak.cutoff_fwhm = FULLPROF_WDT
project.experiments.add(experiment)
experiment = ExperimentFactory.from_scratch(
name='pbso4',
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='pbso4', scale=FULLPROF_SCALE)
experiment.instrument.setup_wavelength = FULLPROF_WAVELENGTH_1
experiment.instrument.setup_wavelength_2 = FULLPROF_WAVELENGTH_2
experiment.instrument.setup_wavelength_2_to_1_ratio = FULLPROF_WAVELENGTH_2_TO_1_RATIO
experiment.instrument.calib_twotheta_offset = FULLPROF_ZERO
experiment.peak.type = 'pseudo-voigt + berar-baldinozzi asymmetry'
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.asym_beba_a0 = FULLPROF_ASY_1
experiment.peak.asym_beba_b0 = FULLPROF_ASY_2
experiment.peak.asym_beba_a1 = FULLPROF_ASY_3
experiment.peak.asym_beba_b1 = FULLPROF_ASY_4
# FullProf excludes 0-10 deg and 154-180 deg in the PCR.
experiment.excluded_regions.create(id='1', start=0.0, end=10.0)
experiment.excluded_regions.create(id='2', start=154.0, end=180.0)
experiment.peak.cutoff_fwhm = FULLPROF_WDT
project.experiments.add(experiment)
⚠️ 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 'pbso4' changed to
pseudo-voigt + berar-baldinozzi asymmetry
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(
'pbso4',
reference=verify.restrict_to_included(experiment, 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(
'pbso4',
reference=verify.restrict_to_included(experiment, calc_fullprof),
candidate=calc_ed_cryspy,
reference_label=FULLPROF_LABEL,
candidate_label=LABEL_ED_CRYSPY,
)
Calculator for experiment 'pbso4' already set to
cryspy
Loading plot…
Fit edi-cryspy to FullProf¶
In [8]:
Copied!
# The anomalous-dispersion table discrepancy is partially compensated by
# scale and isotropic ADPs. The asymmetry terms are refined separately
# because cryspy and FullProf use different Berar-Baldinozzi conventions.
for atom_id in ('Pb', 'S', 'O1', 'O2', 'O3'):
atom = structure.atom_sites[atom_id]
atom.adp_iso.free = True
experiment.linked_structures['pbso4'].scale.free = True
experiment.peak.asym_beba_a0.free = True
experiment.peak.asym_beba_a1.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(
'pbso4',
reference=verify.restrict_to_included(experiment, calc_fullprof),
candidate=calc_ed_cryspy_refined,
reference_label=FULLPROF_LABEL,
candidate_label=LABEL_ED_CRYSPY_REFINED,
)
# The anomalous-dispersion table discrepancy is partially compensated by
# scale and isotropic ADPs. The asymmetry terms are refined separately
# because cryspy and FullProf use different Berar-Baldinozzi conventions.
for atom_id in ('Pb', 'S', 'O1', 'O2', 'O3'):
atom = structure.atom_sites[atom_id]
atom.adp_iso.free = True
experiment.linked_structures['pbso4'].scale.free = True
experiment.peak.asym_beba_a0.free = True
experiment.peak.asym_beba_a1.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(
'pbso4',
reference=verify.restrict_to_included(experiment, calc_fullprof),
candidate=calc_ed_cryspy_refined,
reference_label=FULLPROF_LABEL,
candidate_label=LABEL_ED_CRYSPY_REFINED,
)
Standard fitting
📋 Using experiment 🔬 'pbso4' for 'single' fitting
🚀 Starting fit process with 'lmfit (leastsq)'...
📈 Goodness-of-fit progress:
| iteration | time (s) | χ² | change / status | |
|---|---|---|---|---|
| 1 | 1 | 0.22 | 236207.78 | |
| 2 | 12 | 3.19 | 62567.92 | 73.5% ↓ |
| 3 | 21 | 5.83 | 1188.68 | 98.1% ↓ |
| 4 | 30 | 8.21 | 1078.96 | 9.2% ↓ |
| 5 | 48 | 13.50 | 1078.96 | |
| 6 | 58 | 16.06 | 1078.96 |
🏆 Best goodness-of-fit (reduced χ²) is 1078.96 at iteration 57
✅ 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) | 16.06 |
| 4 | 🔁 Iterations | 55 |
| 5 | 📏 Goodness-of-fit (reduced χ²) | 1078.96 |
| 6 | 📏 R-factor (Rf, %) | 7.81 |
| 7 | 📏 R-factor squared (Rf², %) | 3.66 |
| 8 | 📏 Weighted R-factor (wR, %) | 3.66 |
📈 Refined parameters:
| datablock | category | entry | parameter | units | start | value | s.u. | change | |
|---|---|---|---|---|---|---|---|---|---|
| 1 | pbso4 | atom_site | Pb | adp_iso | Ų | 1.8629 | 3.3782 | 0.0095 | 81.34 % ↑ |
| 2 | pbso4 | atom_site | S | adp_iso | Ų | 1.3497 | 2.5513 | 0.0486 | 89.03 % ↑ |
| 3 | pbso4 | atom_site | O1 | adp_iso | Ų | 1.1971 | 3.4652 | 0.1333 | 189.46 % ↑ |
| 4 | pbso4 | atom_site | O2 | adp_iso | Ų | 2.5617 | 5.3610 | 0.1345 | 109.27 % ↑ |
| 5 | pbso4 | atom_site | O3 | adp_iso | Ų | 1.3746 | 3.2684 | 0.0849 | 137.76 % ↑ |
| 6 | pbso4 | linked_structure | pbso4 | scale | 0.0005 | 0.0008 | 0.0000 | 69.25 % ↑ | |
| 7 | pbso4 | peak | asym_beba_a0 | -0.4136 | 0.4610 | 0.0066 | 211.46 % ↓ | ||
| 8 | pbso4 | peak | asym_beba_a1 | 1.2678 | -1.3688 | 0.0140 | 207.97 % ↓ |
• 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…
In [9]:
Copied!
experiment.linked_structures['pbso4'].scale
experiment.linked_structures['pbso4'].scale
Out[9]:
<pbso4.linked_structure.pbso4.scale = 0.0007959958031394087 ± 6.721696914144178e-07 (free=True)>
Agreement check¶
In [10]:
Copied!
verify.assert_patterns_agree(
[
(
f'{LABEL_ED_CRYSPY} vs {FULLPROF_LABEL}',
verify.restrict_to_included(experiment, calc_fullprof),
calc_ed_cryspy,
),
(
f'{LABEL_ED_CRYSPY_REFINED} vs {FULLPROF_LABEL}',
verify.restrict_to_included(experiment, calc_fullprof),
calc_ed_cryspy_refined,
),
],
known_discrepancy=True,
reason=(
'Laboratory X-ray PbSO4: the remaining strict-tolerance '
'difference is partially compensated by refining scale and '
'isotropic ADPs, consistent with a Cu Kalpha '
'anomalous-dispersion table mismatch in f-prime/f-double-prime, '
'especially for Pb. The Berar-Baldinozzi asymmetry terms are '
'refined separately because cryspy and FullProf use different '
'conventions.'
),
)
verify.assert_patterns_agree(
[
(
f'{LABEL_ED_CRYSPY} vs {FULLPROF_LABEL}',
verify.restrict_to_included(experiment, calc_fullprof),
calc_ed_cryspy,
),
(
f'{LABEL_ED_CRYSPY_REFINED} vs {FULLPROF_LABEL}',
verify.restrict_to_included(experiment, calc_fullprof),
calc_ed_cryspy_refined,
),
],
known_discrepancy=True,
reason=(
'Laboratory X-ray PbSO4: the remaining strict-tolerance '
'difference is partially compensated by refining scale and '
'isotropic ADPs, consistent with a Cu Kalpha '
'anomalous-dispersion table mismatch in f-prime/f-double-prime, '
'especially for Pb. The Berar-Baldinozzi asymmetry terms are '
'refined separately because cryspy and FullProf use different '
'conventions.'
),
)
| Comparison | Metric | Expected | Actual | OK | |
|---|---|---|---|---|---|
| 1 | edi 0.19.0 (cryspy 0.12.1) vs FullProf 7.95 | Profile diff (%) | < 2.5 | 54.11 | ❌ |
| 2 | Max deviation (%) | < 6 | 71.43 | ❌ | |
| 3 | Area ratio | 0.99 to 1.01 | 0.7406 | ❌ | |
| 4 | Shape correlation | > 0.999 | 0.8523 | ❌ | |
| 5 | edi 0.19.0 (cryspy 0.12.1, refined) vs FullProf 7.95 | Profile diff (%) | < 2.5 | 3.66 | ❌ |
| 6 | Max deviation (%) | < 6 | 1.66 | ✅ | |
| 7 | Area ratio | 0.99 to 1.01 | 0.9509 | ❌ | |
| 8 | Shape correlation | > 0.999 | 0.9994 | ✅ |
• Known discrepancy = Laboratory X-ray PbSO4: the remaining strict-tolerance difference is partially compensated by refining scale and isotropic ADPs, consistent with a Cu Kalpha anomalous-dispersion table mismatch in f-prime/f-double-prime, especially for Pb. The Berar-Baldinozzi asymmetry terms are refined separately because cryspy and FullProf use different conventions.
Out[10]:
True