diff --git a/src/nmreval/clib/diffusion.c b/src/nmreval/clib/diffusion.c new file mode 100644 index 0000000..a1c71b6 --- /dev/null +++ b/src/nmreval/clib/diffusion.c @@ -0,0 +1,16 @@ +/* integrands used in quadrature integration with scipy's LowLevelCallables */ +#include + +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; +} \ No newline at end of file diff --git a/src/nmreval/clib/diffusion.so b/src/nmreval/clib/diffusion.so new file mode 100755 index 0000000..8bc6c6f Binary files /dev/null and b/src/nmreval/clib/diffusion.so differ diff --git a/src/nmreval/distributions/helper.py b/src/nmreval/distributions/helper.py index b165f27..4927831 100644 --- a/src/nmreval/distributions/helper.py +++ b/src/nmreval/distributions/helper.py @@ -5,6 +5,17 @@ from ctypes import CDLL, c_double, c_void_p 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 try: lib = CDLL(str(Path(__file__).parents[1] / 'clib' / 'integrate.so')) @@ -39,10 +50,8 @@ try: lib.energyDistSuscImag.restype = c_double lib.energyDistSuscImag.argtypes = (c_double, c_void_p) - HAS_C_FUNCS = True logger.info('Use C functions') except OSError: HAS_C_FUNCS = False logger.info('Use python functions') - diff --git a/src/nmreval/models/diffusion.py b/src/nmreval/models/diffusion.py index b84d08e..fc492f7 100644 --- a/src/nmreval/models/diffusion.py +++ b/src/nmreval/models/diffusion.py @@ -1,7 +1,11 @@ +from ctypes import c_double, cast, c_void_p, pointer + 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 nmreval.distributions.helper import HAS_C_FUNCS, diffusion_lib class Diffusion: @@ -103,13 +107,29 @@ class AnisotropicDiffusion(object): tp = x 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 - z = np.sqrt(q_squared * (d_par - d_perp) * t) # 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: