Source code for epdfsuite.lobato_scattering

"""
Lobato parametrization for electron and X-ray scattering factors.

Based on:
Lobato, I., & Van Dyck, D. (2014). An accurate parameterization for scattering 
factors, electron densities and electrostatic potentials for neutral atoms that 
obey all physical constraints. Acta Crystallographica Section A, 70(6), 636-649.

This is a simplified standalone implementation extracted from abTEM to avoid
heavy dependencies on visualization libraries.
"""

import numpy as np
import json
from pathlib import Path


# Lobato parameters for all elements (H to Lr)
_JSON_PATH = Path(__file__).parent / "lobato_parameters.json"
with _JSON_PATH.open() as _f:
    LOBATO_PARAMETERS = json.load(_f)



[docs] def electron_scattering_factor(k2, params): """ Compute electron scattering factor using Lobato parametrization. Parameters ---------- k2 : float or ndarray Squared scattering vector magnitude (in 1/Angstrom^2) params : ndarray Lobato parameters [2 x 5] array where params[0] are 'a' coefficients and params[1] are 'b' coefficients Returns ------- f : float or ndarray Electron scattering factor Notes ----- Based on Lobato & Van Dyck (2014), Acta Cryst. A70, 636-649 f_e(k^2) = sum_i [ a_i * (2 + b_i * k^2) / (1 + b_i * k^2)^2 ] """ p = np.array(params, dtype=np.float64) # Vectorized computation over all 5 terms k2 = np.asarray(k2) result = np.zeros_like(k2, dtype=np.float64) for i in range(5): result += (p[0, i] * (2.0 + p[1, i] * k2) / (1.0 + p[1, i] * k2) ** 2) return result
[docs] def x_ray_scattering_factor(k, params): """ Compute X-ray scattering factor using Lobato parametrization. Parameters ---------- k : float or ndarray Scattering vector magnitude (in 1/Angstrom) params : ndarray Lobato parameters [2 x 5] array Returns ------- f : float or ndarray X-ray scattering factor Notes ----- Uses Bohr radius conversion: 1 Bohr = 0.529177 Angstrom Based on Lobato & Van Dyck (2014), Acta Cryst. A70, 636-649 f_xray(k) = sum_i [ 2*pi^2*a0 * a_i / (b_i * (1 + b_i * k^2)^2) ] where a0 is the Bohr radius in Angstrom """ BOHR_TO_ANGSTROM = 0.529177210903 p = np.array(params, dtype=np.float64) k = np.asarray(k) k2 = k**2 result = np.zeros_like(k, dtype=np.float64) for i in range(5): result += (2 * np.pi**2 * BOHR_TO_ANGSTROM * p[0, i] / (p[1, i] * (1 + p[1, i] * k2) ** 2)) return result
[docs] def compute_scattering_profile(elements, s_values, xray=False): """ Compute scattering factor profiles for multiple elements. Parameters ---------- elements : list of str List of element symbols (e.g., ['Au', 'C']) s_values : ndarray Scattering vector magnitudes in 1/Angstrom (s = sin(theta)/lambda) xray : bool, optional If True, compute X-ray scattering factors. If False (default), compute electron scattering factors. Returns ------- profiles : ndarray Array of shape (n_elements, n_points) with scattering profiles """ n_elements = len(elements) n_points = len(s_values) profiles = np.zeros((n_elements, n_points), dtype=np.float64) for i, element in enumerate(elements): if element not in LOBATO_PARAMETERS: raise ValueError(f"Element '{element}' not found in Lobato parameters") params = LOBATO_PARAMETERS[element] if xray: # For X-rays: f(k) where k = s profiles[i] = x_ray_scattering_factor(s_values, params) else: # For electrons: f(k^2) where k^2 = s^2 # This is vectorized now k2_values = s_values**2 profiles[i] = electron_scattering_factor(k2_values, params) # For electrons: use k^2 = s^2 k2_values = s_values**2 profiles[i] = np.array([electron_scattering_factor(k2, params) for k2 in k2_values]) return profiles
[docs] class LobatoScatteringCalculator: """ Calculator for electron and X-ray scattering factors using Lobato parametrization. This provides a simplified interface similar to abtem's LobatoParametrization. """ def __init__(self): self.parameters = LOBATO_PARAMETERS
[docs] def line_profiles(self, elements, cutoff, sampling, name="scattering_factor"): """ Compute scattering factor line profiles. Parameters ---------- elements : list of str Element symbols cutoff : float Maximum s value (1/Angstrom) sampling : float Sampling interval in s (1/Angstrom) name : str, optional Type of scattering factor: "scattering_factor" for electrons, "x_ray_scattering_factor" for X-rays Returns ------- result : SimpleNamespace Object with 'array' attribute containing profiles of shape (n_elements, n_points) """ from types import SimpleNamespace # Generate s grid n_points = int(np.ceil(cutoff / sampling)) s_values = np.arange(n_points) * sampling # Determine if X-ray or electron xray = (name == "x_ray_scattering_factor") # Compute profiles profiles = compute_scattering_profile(elements, s_values, xray=xray) # Return in a format compatible with abtem's return structure result = SimpleNamespace(array=profiles) return result
[docs] def get_parameters(self, element): """Get Lobato parameters for a specific element.""" if element not in self.parameters: raise ValueError(f"Element '{element}' not found") return np.array(self.parameters[element])