{ "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": [ "
Loading plot…
" ], "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": [ "
iterationtime (s)χ²change / status
110.04687.66
250.160.02100.0% ↓
380.290.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": [ "
NameValueDescription
1max_iterations1000Maximum solver iterations.
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "📋 Least-squares fit results:\n" ] }, { "data": { "text/html": [ "
MetricValue
1🧪 Minimizerlmfit (leastsq)
2✅ Overall statussuccess
3⏱️ Fitting time (seconds)0.29
4🔁 Iterations5
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": [ "
datablockcategoryentryparameterunitsstartvalues.u.change
1liflinked_structurelifscale0.01000.02000.000099.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": [ "
Loading plot…
" ], "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": [ "
ComparisonMetricExpectedActualOK
1edi 0.20.1 (cryspy 0.13.0) vs FullProf 8.40Profile diff (%)< 2.549.98
2Max deviation (%)< 649.99
3Area ratio0.99 to 1.010.5001
4Shape correlation> 0.9991.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": [ "
ComparisonMetricExpectedActualOK
1edi 0.20.1 (cryspy 0.13.0, refined) vs FullProf 8.40Profile diff (%)< 2.50.28
2Max deviation (%)< 60.20
3Area ratio0.99 to 1.010.9998
4Shape correlation> 0.9991.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 }