5 Commits
dev ... master

Author SHA1 Message Date
5975c08fb2 changing default order of hdf works with node names and displays correct label (#310)
All checks were successful
Build AppImage / Explore-Gitea-Actions (push) Successful in 2m31s
Fix problem with incorrect selection of label and group of HDF files when using not default order

Co-authored-by: Dominik Demuth <dominik.demuth@physik.tu-darmstadt.de>
Reviewed-on: #310
2025-06-03 17:53:48 +00:00
47feebe22f use C function and integration for anisotropic diffusion
All checks were successful
Build AppImage / Explore-Gitea-Actions (push) Successful in 2m18s
2025-05-02 11:35:01 +00:00
11fe47bef1 add sigmoid function to basic models (#307)
All checks were successful
Build AppImage / Explore-Gitea-Actions (push) Successful in 2m13s
fixes #306

Co-authored-by: Dominik Demuth <dominik.demuth@physik.tu-darmstadt.de>
Reviewed-on: #307
2025-02-11 17:31:00 +00:00
f33643955b 304-fitparameter (#305)
Some checks failed
Build AppImage / Explore-Gitea-Actions (push) Has been cancelled
fixes #304

Co-authored-by: Dominik Demuth <dominik.demuth@physik.tu-darmstadt.de>
Reviewed-on: #305
2025-01-23 18:11:45 +00:00
41353b9a54 wide-line spectra handle missing x values better (#303)
All checks were successful
Build AppImage / Explore-Gitea-Actions (push) Successful in 2m37s
see issue #302

Co-authored-by: Dominik Demuth <dominik.demuth@physik.tu-darmstadt.de>
Reviewed-on: #303
2024-12-09 13:45:07 +00:00
9 changed files with 101 additions and 19 deletions

View File

@ -133,7 +133,7 @@ class QAsciiReader(QtWidgets.QDialog, Ui_ascii_reader):
if self.column_checkBox.isChecked() and self.line_spinBox.isEnabled():
header_line = self.reader.header[self.line_spinBox.value()-1]
header_line = header_line.strip('\n\t\r, ')
header_line = re.sub(r'[\t ;,]+', ';', header_line)
header_line = re.sub(r'[\t, ;]+(?!\w*})', ';', header_line)
self.ascii_table.setHorizontalHeaderLabels(header_line.split(';'))

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
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')

View File

@ -49,7 +49,7 @@ class AsciiReader:
with self.fname.open('r') as f:
for i, line in enumerate(islice(f, len(self.header)+len(self.lines), num_lines)):
line = line.strip('\n\t\r, ')
line = re.sub(r'[\t ;,]+', ';', line)
line = re.sub(r'[\t, ;]+(?!\w*})', ';', line)
line = line.split(';')
try:
@ -146,10 +146,11 @@ class AsciiReader:
raw_data = raw_data.reshape((1, *raw_data.shape))
if len(x) == 0 or raw_data.shape[2] == 1:
_temp = np.zeros((raw_data.shape[0], raw_data.shape[1], raw_data.shape[2]+1))
_temp[:, :, 0] = np.arange(raw_data.shape[1])
_temp[:, :, 1:] = raw_data
raw_data = _temp
raw_data = raw_data.reshape(raw_data.shape[0], raw_data.shape[2], raw_data.shape[1])
# _temp = np.zeros((raw_data.shape[0], raw_data.shape[2], raw_data.shape[1]))
# _temp[:, :, 0] = np.arange(raw_data.shape[1])
# _temp[:, :, 1:] = raw_data
# raw_data = _temp
if y:
y = [i+1 for i in y]

View File

@ -302,15 +302,26 @@ class HdfReader(HdfNode):
def make_signal(self, node, flag: str = 'fid', value: str = None, group: str = None):
if value is None:
data_name = node.name
value = self._get_parameter_values(node, node.parameter)
else:
try:
data_name = f"{value}={node.parameter[value]}"
value = node.parameter[value]
except KeyError:
print(node.title_parameter)
try:
value = node.title_parameter[1][value]
temp = node
while value != temp.title_parameter[0][0]:
if temp.parent is None:
break
temp = temp.parent
value = temp.title_parameter[0][1]
data_name = temp.name
except KeyError:
print(f'{value} is not a valid key for {node.name}')
data_name = node.name
value = None
if group is None:
@ -343,11 +354,11 @@ class HdfReader(HdfNode):
dw = float(index['dwelltime'])
if flag == 'fid':
x = np.arange(len(y)) * dw
ret = FID(x, y, name=node.name, value=value, group=group, filename=self.file.filename)
ret = FID(x, y, name=data_name, value=value, group=group, filename=self.file.filename)
elif flag == 'spectrum':
x = np.linspace(-1/dw, 1/dw, num=len(y))
ret = Spectrum(x, y, name=node.name, value=value, group=group, filename=self.file.filename)
ret = Spectrum(x, y, name=data_name, value=value, group=group, filename=self.file.filename)
else:
raise ValueError(f'{flag} unknown, use `fid` or `spectrum`.')

View File

@ -203,6 +203,31 @@ class Sinc:
return c * np.sinc(((x-x0)/w)/np.pi)
class Sigmoid:
type = 'Basic'
name = 'Sigmoid'
equation = 'C / [1 + exp(-a * (x - x_{0})] + y_{0}'
params = ['C', 'a', 'x_{0}', 'y_{0}']
@staticmethod
def func(x, c, a, x0, y0):
"""
Sigmoid function
.. math::
y = C / [1 + exp(-a * (x - x_0))] + y_0
Args:
x (array_like): Input values
c (float): Prefactor
a (float): Steepness of the sigmoid
x0 (float): x position of the sigmoid's midpoint
y0 (float): y position of the sigmoid's midpoint
"""
return c / (1 + np.exp(-a * (x - x0))) + y0
class Sine:
"""
Wavy sine function

View File

@ -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,20 +107,36 @@ 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:
name = 'Diffusion + Cross-Relaxation'
type = 'Diffusion'
equation = r'Diffusion with cross-relax f(ast) \rightarrow s(low)'
params = ['M_{0}', 'D', 'T_{1,f}', 'T_{1,s}', 'k', 'p_{f}', 't_{ev}', 'g']
params = ['M_{0}', 'D', 'T_{1f}', 'T_{1s}', 'k', 'p_{f}', 't_{ev}', 'g']
bounds = [(0, None), (0, None), (0, None), (0, None), (0, None), (0, None)]
choices = [(r'\gamma', 'nucleus', gamma)]

View File

@ -75,7 +75,7 @@ class TwoSatRecAbsolute:
type = 'Relaxation'
name = 'Two-step relaxation (abs. int)'
equation = r'M_{0} + \Sigma \DeltaM_{i}(1-exp(-(x/T_{1,i})^{\beta_{i}}))'
params = [r'\DeltaM_{1}', 'T_{1,1}', r'\beta_{1}', r'\DeltaM_{2}', 'T_{1,2}', r'\beta_{2}', 'M_{0}']
params = [r'\DeltaM_{1}', 'T_{11}', r'\beta_{1}', r'\DeltaM_{2}', 'T_{12}', r'\beta_{2}', 'M_{0}']
choices = [('Type', 'is_inv', {'Saturation': False, 'Inversion': True})]
bounds = [(0, None), (0, None), (0, 1), (0, None), (0, None), (0, 1), (None, None)]
@ -92,7 +92,7 @@ class TwoSatRecRelative:
name = 'Two-step relaxation (rel. int)'
equation = r'M_{0} + \DeltaM[R(1-exp(-(x/T_{1,1})^{\beta_{1}})) + \n'\
r'(1-R)(1-exp(-(x/T_{1,2})^{\beta_{2}}))]'
params = [r'\DeltaM', 'M_{0}', 'T_{1,1}', r'\beta_{1}', 'T_{1,2}', r'\beta_{2}', 'R']
params = [r'\DeltaM', 'M_{0}', 'T_{11}', r'\beta_{1}', 'T_{12}', r'\beta_{2}', 'R']
choices = [('Type', 'kind', {'Saturation': 'sat', 'Inversion': 'inv'})]
bounds = [(0, None), (None, None), (0, None), (0, 1), (0, None), (0, 1), (0, 1)]