use C function and integration for anisotropic diffusion
All checks were successful
Build AppImage / Explore-Gitea-Actions (push) Successful in 2m18s

This commit is contained in:
Dominik Demuth 2025-05-02 11:35:01 +00:00
parent 11fe47bef1
commit 47feebe22f
4 changed files with 52 additions and 7 deletions

View File

@ -0,0 +1,16 @@
/* integrands used in quadrature integration with scipy's LowLevelCallables */
#include <math.h>
double anistropicDiffusion(double x, void *user_data) {
double *c = (double *)user_data;
double q = c[0];
double t = c[1];
double d_perp = c[2];
double d_par = c[3];
double cos_theta = cos(x);
double sin_theta = sin(x);
return exp(-q * q * t * (d_par * cos_theta * cos_theta + d_perp * sin_theta * sin_theta)) * sin_theta;
}

BIN
src/nmreval/clib/diffusion.so Executable file

Binary file not shown.

View File

@ -5,6 +5,17 @@ from ctypes import CDLL, c_double, c_void_p
from ..lib.logger import logger from ..lib.logger import logger
diffusion_lib = None
try:
diffusion_lib = CDLL(str(Path(__file__).parents[1] / 'clib' / 'diffusion.so'))
diffusion_lib.anistropicDiffusion.restype = c_double
diffusion_lib.anistropicDiffusion.argtypes = (c_double, c_void_p)
HAS_C_FUNCS = True
except OSError:
HAS_C_FUNCS = False
lib = None lib = None
try: try:
lib = CDLL(str(Path(__file__).parents[1] / 'clib' / 'integrate.so')) lib = CDLL(str(Path(__file__).parents[1] / 'clib' / 'integrate.so'))
@ -39,10 +50,8 @@ try:
lib.energyDistSuscImag.restype = c_double lib.energyDistSuscImag.restype = c_double
lib.energyDistSuscImag.argtypes = (c_double, c_void_p) lib.energyDistSuscImag.argtypes = (c_double, c_void_p)
HAS_C_FUNCS = True HAS_C_FUNCS = True
logger.info('Use C functions') logger.info('Use C functions')
except OSError: except OSError:
HAS_C_FUNCS = False HAS_C_FUNCS = False
logger.info('Use python functions') logger.info('Use python functions')

View File

@ -1,7 +1,11 @@
from ctypes import c_double, cast, c_void_p, pointer
import numpy as np import numpy as np
from scipy import special as special from scipy import special as special, LowLevelCallable
from scipy.integrate import quad
from ..utils import gamma from ..utils import gamma
from nmreval.distributions.helper import HAS_C_FUNCS, diffusion_lib
class Diffusion: class Diffusion:
@ -103,13 +107,29 @@ class AnisotropicDiffusion(object):
tp = x tp = x
relax = np.exp(-(tp/trel)**brel)*np.exp(-(tp/trel)**brel) relax = np.exp(-(tp/trel)**brel)*np.exp(-(tp/trel)**brel)
q_squared = np.power(g * nucleus * tp, 2) q = g * nucleus * tp
t = 2 * tp / 3 + tm t = 2 * tp / 3 + tm
z = np.sqrt(q_squared * (d_par - d_perp) * t)
# Callaghan eq (6.89) # Callaghan eq (6.89)
diffs = np.exp(-q_squared*t*d_perp) * special.erf(z) / z if HAS_C_FUNCS:
# divide by 2 to normalize by integral sin(x), x=0..pi
diffusion_decay = AnisotropicDiffusion._integrate_c(q, t, d_perp, d_par) / 2
else:
z = np.sqrt(q**2 * (d_par - d_perp) * t)
diffusion_decay = np.exp(-q**2 * t * d_perp) * special.erf(z) / z
return m0 * diffs * relax return m0 * diffusion_decay * relax
@staticmethod
def _integrate_c(q, t, d_perp, d_par) -> np.ndarray:
diffusion_decay = np.zeros_like(t)
for (i, t_i) in enumerate(t):
c = (c_double * 4)(q, t_i, d_perp, d_par)
user_data = cast(pointer(c), c_void_p)
diffusion_decay[i] = quad(LowLevelCallable(diffusion_lib.anistropicDiffusion, user_data), 0, np.pi, epsabs=1e-13)[0]
return diffusion_decay
class Peschier: class Peschier: