#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Interpolation utilities for EPR simulation data.
This module provides the :class:`Interpolator` class to perform 3D interpolation
of EPR simulation results (intensities, field positions, linewidths) on spherical
grids with different symmetries (Dooh and general cases).
© M. Sc. Florian Quintes, 2026.
@contact: florian.quintes@pc.uni.freiburg.de
@author: Florian Quintes
"""
import numpy as np
from scipy.interpolate import RBFInterpolator, CubicSpline
[docs]
class Interpolator:
"""
Spherical data interpolator for EPR simulations.
Handles interpolation of EPR simulation results on spherical grids with
different symmetries (Dooh and general cases). Supports interpolation
of intensities, field positions, linewidths, and transition matrices.
Parameters
----------
theta : np.array
Original theta angles in radians.
phi : np.array
Original phi angles in radians.
data : tuple
Tuple containing (field_positions, intensities, linewidths, transitions).
Attributes
----------
_Dooh : bool
Whether the data has Dooh symmetry.
_xyz : np.array
Cartesian coordinates of original data points.
_res_fields : np.array
Resonance field positions.
_intensities : np.array
Signal intensities.
_widths : np.array
Linewidths.
_transitions : np.array
Transition matrices.
"""
def __init__(self, theta: np.array, phi: np.array, data: tuple):
"""
Initialize the interpolator with simulation data.
Parameters
----------
theta : np.array
Original theta angles in radians.
phi : np.array
Original phi angles in radians.
data : tuple
Tuple containing (field_positions, intensities, linewidths, transitions).
"""
self._theta_or = theta
self._phi_or = phi
self._Dooh = np.allclose(phi, np.zeros(phi.shape))
self._xyz = self._get_xyz(self._theta_or, self._phi_or)
(
self._res_fields,
self._intensities,
self._widths,
self._transitions,
) = data
self._transitions = self._transitions[0]
if self._Dooh:
self._init_intensity_interpolator_dooh()
self._init_position_interpolator_dooh()
self._init_width_interpolator_dooh()
else:
self._init_intensity_interpolator()
self._init_position_interpolator()
self._init_width_interpolator()
[docs]
def get_intensities(self, theta, phi) -> np.array:
"""
Interpolate signal intensities for given angles.
Parameters
----------
theta : np.array
Theta angles in radians.
phi : np.array
Phi angles in radians.
Returns
-------
np.array
Interpolated intensities.
"""
if self._Dooh:
return self._intens_interp(theta)
else:
xyz = self._get_xyz(theta, phi)
return self._intens_interp(xyz)
[docs]
def get_positions(self, theta, phi) -> np.array:
"""
Interpolate resonance field positions for given angles.
Parameters
----------
theta : np.array
Theta angles in radians.
phi : np.array
Phi angles in radians.
Returns
-------
np.array
Interpolated field positions.
"""
if self._Dooh:
return self._pos_interp(theta)
else:
xyz = self._get_xyz(theta, phi)
return self._pos_interp(xyz)
[docs]
def get_widths(self, theta, phi) -> np.array:
"""
Interpolate linewidths for given angles.
Parameters
----------
theta : np.array
Theta angles in radians.
phi : np.array
Phi angles in radians.
Returns
-------
np.array
Interpolated linewidths.
"""
if self._Dooh:
return self._widths_interp(theta)
else:
xyz = self._get_xyz(theta, phi)
return self._widths_interp(xyz)
[docs]
def get_transitions(self, grid_points: int) -> np.array:
"""
Get transition matrices for given grid points.
Parameters
----------
grid_points : int
Number of grid points.
Returns
-------
np.array
Transition matrices repeated for each grid point.
"""
return np.repeat(self._transitions[np.newaxis, :, :], grid_points, axis=0)
def _init_intensity_interpolator(self):
"""
Initialize general 3D intensity interpolator.
Uses radial basis function interpolation with linear kernel.
"""
intensities = self._intensities # TODO
self._intens_interp = RBFInterpolator(
self._xyz, intensities, neighbors=6, smoothing=0, kernel="linear"
)
def _init_position_interpolator(self):
"""
Initialize general 3D field position interpolator.
Uses radial basis function interpolation with thin-plate spline kernel.
"""
fields = self._res_fields # TODO
self._pos_interp = RBFInterpolator(
self._xyz,
fields,
neighbors=18,
smoothing=0,
kernel="thin_plate_spline",
)
def _init_width_interpolator(self):
"""
Initialize general 3D linewidth interpolator.
Uses radial basis function interpolation with thin-plate spline kernel.
"""
widths = self._widths # TODO
self._widths_interp = RBFInterpolator(
self._xyz,
widths,
neighbors=18,
smoothing=0,
kernel="thin_plate_spline",
)
def _init_intensity_interpolator_dooh(self):
"""
Initialize intensity interpolator for Dooh symmetry.
Uses cubic spline interpolation along the theta axis.
"""
intensities = self._intensities # TODO
self._intens_interp = CubicSpline(self._theta_or, intensities)
def _init_position_interpolator_dooh(self):
"""
Initialize field position interpolator for Dooh symmetry.
Uses cubic spline interpolation along the theta axis.
"""
fields = self._res_fields # TODO
self._pos_interp = CubicSpline(self._theta_or, fields)
def _init_width_interpolator_dooh(self):
"""
Initialize linewidth interpolator for Dooh symmetry.
Uses cubic spline interpolation along the theta axis.
"""
widths = self._widths # TODO
self._widths_interp = CubicSpline(self._theta_or, widths)
def _get_xyz(self, theta, phi):
"""
Convert spherical coordinates to Cartesian coordinates.
Parameters
----------
theta : np.array
Theta angles in radians.
phi : np.array
Phi angles in radians.
Returns
-------
np.array
Cartesian coordinates (x, y, z).
"""
x = np.sin(theta) * np.cos(phi)
y = np.sin(theta) * np.sin(phi)
z = np.cos(theta)
return np.array([x, y, z]).T