{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"id": "0",
"metadata": {
"execution": {
"iopub.execute_input": "2026-08-18T18:19:01.883793Z",
"iopub.status.busy": "2026-08-18T18:19:01.883614Z",
"iopub.status.idle": "2026-08-18T18:19:01.887854Z",
"shell.execute_reply": "2026-08-18T18:19:01.887140Z"
},
"tags": [
"hide-in-docs"
]
},
"outputs": [],
"source": [
"# Check whether easydiffraction is installed; install it if needed.\n",
"# Required for remote environments such as Google Colab.\n",
"import importlib.util\n",
"\n",
"if importlib.util.find_spec('easydiffraction') is None:\n",
" %pip install easydiffraction==0.20.1"
]
},
{
"cell_type": "markdown",
"id": "1",
"metadata": {},
"source": [
"# LiF — powder X-ray CW — polarization\n",
"\n",
"Verifies the X-ray polarization correction on the same single-wavelength\n",
"LiF reference.\n",
"\n",
"**Refinement:** the overall scale only; all other parameters are\n",
"taken from the FullProf reference."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "2",
"metadata": {
"execution": {
"iopub.execute_input": "2026-08-18T18:19:01.889352Z",
"iopub.status.busy": "2026-08-18T18:19:01.889182Z",
"iopub.status.idle": "2026-08-18T18:19:04.616969Z",
"shell.execute_reply": "2026-08-18T18:19:04.616211Z"
}
},
"outputs": [],
"source": [
"import easydiffraction as edi\n",
"from easydiffraction import ExperimentFactory\n",
"from easydiffraction import StructureFactory\n",
"from easydiffraction.analysis import verification as verify"
]
},
{
"cell_type": "markdown",
"id": "3",
"metadata": {},
"source": [
"## Build the project"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "4",
"metadata": {
"execution": {
"iopub.execute_input": "2026-08-18T18:19:04.619326Z",
"iopub.status.busy": "2026-08-18T18:19:04.619037Z",
"iopub.status.idle": "2026-08-18T18:19:04.908750Z",
"shell.execute_reply": "2026-08-18T18:19:04.907978Z"
}
},
"outputs": [],
"source": [
"project = edi.Project()"
]
},
{
"cell_type": "markdown",
"id": "5",
"metadata": {},
"source": [
"## Define the structure"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "6",
"metadata": {
"execution": {
"iopub.execute_input": "2026-08-18T18:19:04.910906Z",
"iopub.status.busy": "2026-08-18T18:19:04.910733Z",
"iopub.status.idle": "2026-08-18T18:19:04.916659Z",
"shell.execute_reply": "2026-08-18T18:19:04.915968Z"
}
},
"outputs": [],
"source": [
"structure = StructureFactory.from_scratch(name='lif')\n",
"\n",
"structure.space_group.name_h_m = 'F m -3 m' # FullProf Space group symbol\n",
"structure.cell.length_a = 4.026700 # FullProf a\n",
"\n",
"structure.atom_sites.create(\n",
" id='Li1', # FullProf Atom\n",
" type_symbol='Li', # FullProf Typ\n",
" fract_x=0.0, # FullProf X\n",
" fract_y=0.0, # FullProf Y\n",
" fract_z=0.0, # FullProf Z\n",
" adp_type='Biso', # FullProf Biso\n",
" adp_iso=1.20000, # FullProf Biso\n",
")\n",
"structure.atom_sites.create(\n",
" id='F1', # FullProf Atom\n",
" type_symbol='F', # FullProf Typ\n",
" fract_x=0.5, # FullProf X\n",
" fract_y=0.5, # FullProf Y\n",
" fract_z=0.5, # FullProf Z\n",
" adp_type='Biso', # FullProf Biso\n",
" adp_iso=0.80000, # FullProf Biso\n",
")\n",
"\n",
"project.structures.add(structure)"
]
},
{
"cell_type": "markdown",
"id": "7",
"metadata": {},
"source": [
"## Load the FullProf reference"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "8",
"metadata": {
"execution": {
"iopub.execute_input": "2026-08-18T18:19:04.918377Z",
"iopub.status.busy": "2026-08-18T18:19:04.918205Z",
"iopub.status.idle": "2026-08-18T18:19:04.930166Z",
"shell.execute_reply": "2026-08-18T18:19:04.929414Z"
}
},
"outputs": [],
"source": [
"FULLPROF_PROJECT_DIR = 'pd-xray-cwl_lif'\n",
"FULLPROF_PRF_FILE = 'lif_single_polarized.prf'\n",
"FULLPROF_SUM_FILE = 'lif_single_polarized.sum'\n",
"FULLPROF_BAC_FILE = 'lif_single_polarized.bac'\n",
"FULLPROF_LABEL = verify.fullprof_label(FULLPROF_PROJECT_DIR, FULLPROF_SUM_FILE)\n",
"\n",
"FULLPROF_ZERO = 0.0 # FullProf Zero\n",
"FULLPROF_SCALE = 0.01 # FullProf Scale\n",
"FULLPROF_WAVELENGTH = 1.540560 # FullProf Lambda1\n",
"FULLPROF_U = 0.048457 # FullProf U\n",
"FULLPROF_V = -0.083053 # FullProf V\n",
"FULLPROF_W = 0.040000 # FullProf W\n",
"FULLPROF_X = 0.0 # FullProf X\n",
"FULLPROF_Y = 0.049268 # FullProf Y\n",
"FULLPROF_WDT = 48.0 # FullProf Wdt\n",
"FULLPROF_POLARIZATION_COEFFICIENT = 0.5 # FullProf Rpolarz\n",
"FULLPROF_CTHM = 0.8 # FullProf Cthm\n",
"FULLPROF_MONOCHROMATOR_TWOTHETA = 26.5650511771 # acos(sqrt(Cthm)) in degrees\n",
"\n",
"x, calc_fullprof = verify.load_fullprof_calc_profile(\n",
" FULLPROF_PROJECT_DIR,\n",
" FULLPROF_PRF_FILE,\n",
" FULLPROF_BAC_FILE,\n",
" FULLPROF_ZERO,\n",
")"
]
},
{
"cell_type": "markdown",
"id": "9",
"metadata": {},
"source": [
"## Create the experiment"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "10",
"metadata": {
"execution": {
"iopub.execute_input": "2026-08-18T18:19:04.931651Z",
"iopub.status.busy": "2026-08-18T18:19:04.931481Z",
"iopub.status.idle": "2026-08-18T18:19:05.893518Z",
"shell.execute_reply": "2026-08-18T18:19:05.892784Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[1;36mPeak profile type for experiment \u001b[0m\u001b[32m'lif'\u001b[0m\u001b[1;36m changed to\u001b[0m\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"pseudo-voigt\n"
]
}
],
"source": [
"experiment = ExperimentFactory.from_scratch(\n",
" name='lif',\n",
" sample_form='powder',\n",
" beam_mode='constant wavelength',\n",
" radiation_probe='xray',\n",
" scattering_type='bragg',\n",
")\n",
"verify.set_reference_as_measured(experiment, x, calc_fullprof)\n",
"\n",
"experiment.linked_structures.create(structure_id='lif', scale=FULLPROF_SCALE)\n",
"\n",
"experiment.instrument.setup_wavelength = FULLPROF_WAVELENGTH\n",
"experiment.instrument.calib_twotheta_offset = FULLPROF_ZERO\n",
"experiment.instrument.setup_polarization_coefficient = FULLPROF_POLARIZATION_COEFFICIENT\n",
"experiment.instrument.setup_monochromator_twotheta = FULLPROF_MONOCHROMATOR_TWOTHETA\n",
"\n",
"experiment.peak.type = 'pseudo-voigt'\n",
"experiment.peak.broad_gauss_u = FULLPROF_U\n",
"experiment.peak.broad_gauss_v = FULLPROF_V\n",
"experiment.peak.broad_gauss_w = FULLPROF_W\n",
"experiment.peak.broad_lorentz_x = FULLPROF_X\n",
"experiment.peak.broad_lorentz_y = FULLPROF_Y\n",
"\n",
"experiment.peak.cutoff_fwhm = FULLPROF_WDT\n",
"\n",
"project.experiments.add(experiment)"
]
},
{
"cell_type": "markdown",
"id": "11",
"metadata": {},
"source": [
"## edi-cryspy VS FullProf"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "12",
"metadata": {
"execution": {
"iopub.execute_input": "2026-08-18T18:19:05.895445Z",
"iopub.status.busy": "2026-08-18T18:19:05.895238Z",
"iopub.status.idle": "2026-08-18T18:19:06.807169Z",
"shell.execute_reply": "2026-08-18T18:19:06.806225Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[1;36mCalculator for experiment \u001b[0m\u001b[32m'lif'\u001b[0m\u001b[1;36m already set to\u001b[0m\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"cryspy\n"
]
},
{
"data": {
"text/html": [
"
"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"experiment.calculator.type = 'cryspy'\n",
"\n",
"project.analysis.calculate()\n",
"calc_ed_cryspy = experiment.data.intensity_calc\n",
"LABEL_ED_CRYSPY = verify.engine_label('cryspy')\n",
"\n",
"project.display.pattern_comparison(\n",
" 'lif',\n",
" reference=calc_fullprof,\n",
" candidate=calc_ed_cryspy,\n",
" reference_label=FULLPROF_LABEL,\n",
" candidate_label=LABEL_ED_CRYSPY,\n",
")"
]
},
{
"cell_type": "markdown",
"id": "13",
"metadata": {},
"source": [
"## Fit edi-cryspy to FullProf\n",
"\n",
"Cryspy applies polarization natively from the `K` and `cthm` values\n",
"passed by EasyDiffraction. FullProf's characteristic X-ray convention\n",
"for `Rpolarz = 0.5` uses the same polarization expression multiplied\n",
"by two, so the seeded FullProf scale leaves the native Cryspy pattern\n",
"half as intense. This fit documents the convention mismatch by letting\n",
"the scale absorb that factor."
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "14",
"metadata": {
"execution": {
"iopub.execute_input": "2026-08-18T18:19:06.811743Z",
"iopub.status.busy": "2026-08-18T18:19:06.811539Z",
"iopub.status.idle": "2026-08-18T18:19:07.843380Z",
"shell.execute_reply": "2026-08-18T18:19:07.842546Z"
}
},
"outputs": [
{
"data": {
"text/html": [],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/javascript": [
"\n",
"(function() {\n",
" const button = document.getElementById('ed-fit-stop-97bfd3eef9474736a6f2396fcbe7875a-button');\n",
" const status = document.getElementById('ed-fit-stop-97bfd3eef9474736a6f2396fcbe7875a-status');\n",
" const kernelId = '';\n",
" if (!button) {\n",
" return;\n",
" }\n",
"\n",
" function setStatus(text) {\n",
" if (status) {\n",
" status.textContent = text;\n",
" }\n",
" }\n",
"\n",
" function pageConfig() {\n",
" const element = document.getElementById('jupyter-config-data');\n",
" if (!element || !element.textContent) {\n",
" return {};\n",
" }\n",
" try {\n",
" return JSON.parse(element.textContent);\n",
" } catch (error) {\n",
" return {};\n",
" }\n",
" }\n",
"\n",
" function baseUrl(config) {\n",
" const configured = config.baseUrl || config.base_url ||\n",
" (window.Jupyter && Jupyter.notebook && Jupyter.notebook.base_url);\n",
" if (configured) {\n",
" return configured.endsWith('/') ? configured : configured + '/';\n",
" }\n",
" const markers = ['/lab/', '/notebooks/', '/tree/'];\n",
" for (const marker of markers) {\n",
" const index = window.location.pathname.indexOf(marker);\n",
" if (index >= 0) {\n",
" return window.location.pathname.slice(0, index + 1);\n",
" }\n",
" }\n",
" return '/';\n",
" }\n",
"\n",
" function token(config) {\n",
" return config.token || new URLSearchParams(window.location.search).get('token') || '';\n",
" }\n",
"\n",
" function cookie(name) {\n",
" const prefix = name + '=';\n",
" for (const part of document.cookie.split(';')) {\n",
" const trimmed = part.trim();\n",
" if (trimmed.startsWith(prefix)) {\n",
" return decodeURIComponent(trimmed.slice(prefix.length));\n",
" }\n",
" }\n",
" return '';\n",
" }\n",
"\n",
" function notebookPath() {\n",
" const decoded = decodeURIComponent(window.location.pathname);\n",
" const markers = ['/lab/tree/', '/notebooks/', '/tree/'];\n",
" for (const marker of markers) {\n",
" const index = decoded.indexOf(marker);\n",
" if (index >= 0) {\n",
" return decoded.slice(index + marker.length);\n",
" }\n",
" }\n",
" return '';\n",
" }\n",
"\n",
" async function kernelFromSessions(config) {\n",
" const url = new URL(baseUrl(config) + 'api/sessions', window.location.origin);\n",
" const authToken = token(config);\n",
" if (authToken) {\n",
" url.searchParams.set('token', authToken);\n",
" }\n",
" const response = await fetch(url, {credentials: 'same-origin'});\n",
" if (!response.ok) {\n",
" return '';\n",
" }\n",
" const sessions = await response.json();\n",
" const path = notebookPath();\n",
" const session = sessions.find((item) => item.path === path) || sessions[0];\n",
" return session && session.kernel ? session.kernel.id : '';\n",
" }\n",
"\n",
" async function interruptKernel(config, resolvedKernelId) {\n",
" const url = new URL(\n",
" baseUrl(config) + 'api/kernels/' + resolvedKernelId + '/interrupt',\n",
" window.location.origin\n",
" );\n",
" const authToken = token(config);\n",
" if (authToken) {\n",
" url.searchParams.set('token', authToken);\n",
" }\n",
" const xsrfToken = cookie('_xsrf');\n",
" const headers = {};\n",
" if (xsrfToken) {\n",
" headers['X-XSRFToken'] = xsrfToken;\n",
" }\n",
" const response = await fetch(url, {\n",
" method: 'POST',\n",
" credentials: 'same-origin',\n",
" headers: headers\n",
" });\n",
" return response.ok;\n",
" }\n",
"\n",
" button.addEventListener('click', async function() {\n",
" button.disabled = true;\n",
" setStatus('Stopping...');\n",
" const config = pageConfig();\n",
" try {\n",
" const resolvedKernelId = kernelId || await kernelFromSessions(config);\n",
" if (!resolvedKernelId) {\n",
" throw new Error('Could not resolve the current kernel id.');\n",
" }\n",
" const interrupted = await interruptKernel(config, resolvedKernelId);\n",
" if (!interrupted) {\n",
" throw new Error('Jupyter Server rejected the interrupt request.');\n",
" }\n",
" setStatus('Interrupt sent...');\n",
" } catch (error) {\n",
" button.disabled = false;\n",
" setStatus('Use Kernel > Interrupt to stop this fit.');\n",
" }\n",
" });\n",
"})();\n"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[1;36mStandard fitting\u001b[0m\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"📋 Using experiment 🔬 \u001b[32m'lif'\u001b[0m for \u001b[32m'single'\u001b[0m fitting\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"🚀 Starting fit process with \u001b[32m'lmfit \u001b[0m\u001b[32m(\u001b[0m\u001b[32mleastsq\u001b[0m\u001b[32m)\u001b[0m\u001b[32m'\u001b[0m\u001b[33m...\u001b[0m\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"📈 Goodness-of-fit progress:\n"
]
},
{
"data": {
"text/html": [
" | 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 | |
|---|
"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"🏆 Best goodness-of-fit \u001b[1m(\u001b[0mreduced χ²\u001b[1m)\u001b[0m is \u001b[1;36m0.02\u001b[0m at iteration \u001b[1;36m5\u001b[0m\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"✅ Fitting complete.\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"⚙️ Settings used:\n"
]
},
{
"data": {
"text/html": [
" | Name | Value | Description |
|---|
| 1 | max_iterations | 1000 | Maximum solver iterations. |
|---|
"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"📋 Least-squares fit results:\n"
]
},
{
"data": {
"text/html": [
" | 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 |
|---|
"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"📈 Refined parameters:\n"
]
},
{
"data": {
"text/html": [
" | datablock | category | entry | parameter | units | start | value | s.u. | change |
|---|
| 1 | lif | linked_structure | lif | scale | | 0.0100 | 0.0200 | 0.0000 | 99.90 % ↑ |
|---|
"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
" • 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
"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
""
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"experiment.linked_structures['lif'].scale.free = True\n",
"\n",
"project.analysis.fit()\n",
"project.display.fit.results()\n",
"\n",
"project.analysis.calculate()\n",
"calc_ed_cryspy_refined = experiment.data.intensity_calc\n",
"LABEL_ED_CRYSPY_REFINED = verify.engine_label('cryspy', note='refined')\n",
"\n",
"project.display.pattern_comparison(\n",
" 'lif',\n",
" reference=calc_fullprof,\n",
" candidate=calc_ed_cryspy_refined,\n",
" reference_label=FULLPROF_LABEL,\n",
" candidate_label=LABEL_ED_CRYSPY_REFINED,\n",
")"
]
},
{
"cell_type": "markdown",
"id": "15",
"metadata": {},
"source": [
"## Agreement check"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "16",
"metadata": {
"execution": {
"iopub.execute_input": "2026-08-18T18:19:07.849862Z",
"iopub.status.busy": "2026-08-18T18:19:07.849683Z",
"iopub.status.idle": "2026-08-18T18:19:07.861379Z",
"shell.execute_reply": "2026-08-18T18:19:07.860537Z"
}
},
"outputs": [
{
"data": {
"text/html": [
" | 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 | ✅ |
|---|
"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
" • 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.
"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
" | 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 | ✅ |
|---|
"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"verify.assert_patterns_agree(\n",
" [\n",
" (\n",
" f'{LABEL_ED_CRYSPY} vs {FULLPROF_LABEL}',\n",
" calc_fullprof,\n",
" calc_ed_cryspy,\n",
" ),\n",
" ],\n",
" known_discrepancy=True,\n",
" reason=(\n",
" 'Cryspy applies the native K/cthm polarization factor directly, '\n",
" 'while FullProf multiplies the characteristic X-ray Rpolarz=0.5 '\n",
" 'form by two; before scale refinement the Cryspy pattern is '\n",
" 'therefore half as intense.'\n",
" ),\n",
")\n",
"\n",
"verify.assert_patterns_agree([\n",
" (\n",
" f'{LABEL_ED_CRYSPY_REFINED} vs {FULLPROF_LABEL}',\n",
" calc_fullprof,\n",
" calc_ed_cryspy_refined,\n",
" ),\n",
"])"
]
}
],
"metadata": {
"jupytext": {
"cell_metadata_filter": "-all",
"main_language": "python",
"notebook_metadata_filter": "-all"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.14.6"
}
},
"nbformat": 4,
"nbformat_minor": 5
}