more modularity
This commit is contained in:
parent
b88bd0dbd6
commit
b2164c944a
2
LICENSE
2
LICENSE
@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2024 dominik.
|
Copyright (c) 2024 Dominik Demuth
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
33
pyproject.toml
Normal file
33
pyproject.toml
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=61.0"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "rwsims"
|
||||||
|
version = "0.0.1"
|
||||||
|
authors = [
|
||||||
|
{ name="Dominik Demuth", email="dominik.demuth@pkm.tu-darmstadt.de" },
|
||||||
|
]
|
||||||
|
maintainers = [
|
||||||
|
{ name="Dominik Demuth", email="dominik.demuth@pkm.tu-darmstadt.de"}
|
||||||
|
]
|
||||||
|
description = "A small example package"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.8"
|
||||||
|
dependencies = [
|
||||||
|
"numpy",
|
||||||
|
"matplotlib"
|
||||||
|
]
|
||||||
|
classifiers = [
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"License :: OSI Approved :: BSD-3 Clause",
|
||||||
|
"Operating System :: OS Independent",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
rw_spectra = "scripts.sim_spectra"
|
||||||
|
rw_ste = "scripts.sim_ste"
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Homepage = "https://github.com/pypa/sampleproject"
|
||||||
|
Issues = "https://github.com/pypa/sampleproject/issues"
|
@ -1,114 +0,0 @@
|
|||||||
from time import time
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
from scipy.interpolate import interp1d
|
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
|
|
||||||
|
|
||||||
# spectral parameter
|
|
||||||
delta = 161e3 # in Hz
|
|
||||||
eta = 0
|
|
||||||
lb = 2e3 # in Hz
|
|
||||||
|
|
||||||
# correlation time
|
|
||||||
tau = [1e-7] # in s
|
|
||||||
|
|
||||||
# acquisition parameter
|
|
||||||
acq_length = 4096
|
|
||||||
dt = 1e-6 # in s
|
|
||||||
t_echo = [0, 5e-6, 10e-6, 20e-6, 50e-6, 100e-6, 200e-6] # all in s
|
|
||||||
|
|
||||||
# derived parameter
|
|
||||||
t_acq = np.arange(acq_length) * dt
|
|
||||||
t_max = acq_length*dt + 2*max(t_echo)
|
|
||||||
dampening = np.exp(-lb * t_acq)
|
|
||||||
|
|
||||||
# random number generator
|
|
||||||
seed = 1234
|
|
||||||
rng = np.random.default_rng(seed)
|
|
||||||
|
|
||||||
# number of random walkers
|
|
||||||
num_traj = 5000
|
|
||||||
|
|
||||||
|
|
||||||
def omega_q(delta_: float, eta_: float, theta_: float, phi_: float) -> float:
|
|
||||||
cos_theta = np.cos(theta_)
|
|
||||||
sin_theta = np.sin(theta_)
|
|
||||||
return 2 * np.pi * delta_ * (3 * cos_theta * cos_theta - 1 + eta_ * sin_theta*sin_theta * np.cos(2*phi_))
|
|
||||||
|
|
||||||
|
|
||||||
def new_orientation(delta_: float, eta_: float) -> float:
|
|
||||||
z_theta, z_phi = rng.random(2)
|
|
||||||
theta = np.arccos(1 - 2 * z_theta)
|
|
||||||
phi = 2 * np.pi * z_phi
|
|
||||||
|
|
||||||
return omega_q(delta_, eta_, theta, phi)
|
|
||||||
|
|
||||||
|
|
||||||
for tau_i in tau:
|
|
||||||
print(f'\nStart for tau={tau_i}')
|
|
||||||
|
|
||||||
timesignal = np.zeros((acq_length, len(t_echo)))
|
|
||||||
|
|
||||||
start = time()
|
|
||||||
expected_jumps = round(t_max/tau_i)
|
|
||||||
if expected_jumps > 1e7:
|
|
||||||
print(f'Too many jumps to process, Skip {tau_i}s')
|
|
||||||
continue
|
|
||||||
|
|
||||||
for i in range(num_traj):
|
|
||||||
t_passed = 0
|
|
||||||
t = [0]
|
|
||||||
phase = [0]
|
|
||||||
accumulated_phase = 0
|
|
||||||
|
|
||||||
while t_passed < t_max:
|
|
||||||
# orientation until the next jump
|
|
||||||
current_omega = new_orientation(delta, eta)
|
|
||||||
|
|
||||||
# time to next jump
|
|
||||||
t_wait = rng.exponential(tau_i)
|
|
||||||
|
|
||||||
t_passed += t_wait
|
|
||||||
accumulated_phase += t_wait * current_omega
|
|
||||||
|
|
||||||
t.append(t_passed)
|
|
||||||
phase.append(accumulated_phase)
|
|
||||||
|
|
||||||
# convenient interpolation to get phase at arbitrary times
|
|
||||||
phase_interpol = interp1d(t, phase)
|
|
||||||
|
|
||||||
for j, t_echo_j in enumerate(t_echo):
|
|
||||||
# effect of de-phasing and re-phasing
|
|
||||||
start_amp = -2 * phase_interpol(t_echo_j)
|
|
||||||
|
|
||||||
# start of actual acquisition
|
|
||||||
timesignal[:, j] += np.cos(start_amp + phase_interpol(t_acq + 2*t_echo_j)) * dampening
|
|
||||||
|
|
||||||
if (i+1) % 200 == 0:
|
|
||||||
elapsed = time()-start
|
|
||||||
print(f'Step {i+1} of {num_traj}', end=' - ')
|
|
||||||
total = num_traj * elapsed / (i+1)
|
|
||||||
print(f'elapsed: {elapsed:.2f}s - total: {total:.2f}s - remaining: {total-elapsed:.2f}s')
|
|
||||||
|
|
||||||
timesignal /= num_traj
|
|
||||||
|
|
||||||
# FT to spectrum
|
|
||||||
freq = np.fft.fftshift(np.fft.fftfreq(acq_length, dt))
|
|
||||||
spec = np.fft.fftshift(np.fft.fft(timesignal, axis=0), axes=0).real
|
|
||||||
spec -= spec[0]
|
|
||||||
|
|
||||||
t_echo_strings = list(map(str, t_echo))
|
|
||||||
|
|
||||||
# plot spectra
|
|
||||||
fig, ax = plt.subplots()
|
|
||||||
lines = ax.plot(freq, spec)
|
|
||||||
ax.set_title(f'RJ (tau = {tau_i}s)')
|
|
||||||
ax.legend(lines, t_echo_strings)
|
|
||||||
# plt.savefig(f'RJ_{tau_i}.png')
|
|
||||||
|
|
||||||
# # save time signals and spectra
|
|
||||||
# np.savetxt(f'rj_spectrum_{tau_i}.dat', np.c_[freq, spec], header='f\t' + '\t'.join(t_echo_strings))
|
|
||||||
# np.savetxt(f'rj_timesignal_{tau_i}.dat', np.c_[t_acq, timesignal], header='t\t' + '\t'.join(t_echo_strings))
|
|
||||||
|
|
||||||
plt.show()
|
|
@ -1,123 +0,0 @@
|
|||||||
from time import time
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
from scipy.interpolate import interp1d
|
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
|
|
||||||
|
|
||||||
# spectral parameter
|
|
||||||
delta = 161e3 # in Hz
|
|
||||||
eta = 0
|
|
||||||
lb = 5e3 # in Hz
|
|
||||||
|
|
||||||
# correlation time
|
|
||||||
tau = [1e-5] # in s
|
|
||||||
|
|
||||||
# acquisition parameter
|
|
||||||
acq_length = 4096
|
|
||||||
dt = 1e-6 # in s
|
|
||||||
t_echo = [0, 5e-6, 10e-6, 20e-6, 50e-6, 100e-6, 200e-6] # all in s
|
|
||||||
|
|
||||||
# derived parameter
|
|
||||||
t_acq = np.arange(acq_length) * dt
|
|
||||||
t_max = acq_length*dt + 2*max(t_echo)
|
|
||||||
dampening = np.exp(-lb * t_acq)
|
|
||||||
|
|
||||||
# random number generator
|
|
||||||
seed = None
|
|
||||||
rng = np.random.default_rng(seed)
|
|
||||||
|
|
||||||
# number of random walkers
|
|
||||||
num_traj = 50000
|
|
||||||
|
|
||||||
|
|
||||||
def omega_q(delta_: float, eta_: float, theta_: float, phi_: float) -> np.ndarray:
|
|
||||||
cos_theta = np.cos(theta_)
|
|
||||||
sin_theta = np.sin(theta_)
|
|
||||||
return 2 * np.pi * delta_ * (3 * cos_theta * cos_theta - 1 + eta_ * sin_theta*sin_theta * np.cos(2*phi_))
|
|
||||||
|
|
||||||
|
|
||||||
def new_orientation(delta_: float, eta_: float, size=1) -> np.ndarray:
|
|
||||||
z_theta, z_phi = rng.random((2, size))
|
|
||||||
theta = np.arccos(1 - 2 * z_theta)
|
|
||||||
phi = 2 * np.pi * z_phi
|
|
||||||
|
|
||||||
return omega_q(delta_, eta_, theta, phi)
|
|
||||||
|
|
||||||
|
|
||||||
def new_tau(size=1) -> np.ndarray:
|
|
||||||
return rng.exponential(tau_i, size=size)
|
|
||||||
|
|
||||||
|
|
||||||
for tau_i in tau:
|
|
||||||
print(f'\nStart for tau={tau_i}')
|
|
||||||
|
|
||||||
timesignal = np.zeros((acq_length, len(t_echo)))
|
|
||||||
|
|
||||||
start = time()
|
|
||||||
expected_jumps = round(t_max/tau_i)
|
|
||||||
if expected_jumps > 1e7:
|
|
||||||
print(f'Too many jumps to process, Skip {tau_i}s')
|
|
||||||
continue
|
|
||||||
|
|
||||||
chunks = int(0.6 * t_max / tau_i) + 1
|
|
||||||
print(f'Chunk size for trajectories: {chunks}')
|
|
||||||
|
|
||||||
for i in range(num_traj):
|
|
||||||
t_passed = 0
|
|
||||||
t = [0]
|
|
||||||
phase = [0]
|
|
||||||
accumulated_phase = 0
|
|
||||||
|
|
||||||
while t_passed < t_max:
|
|
||||||
# orientation until the next jump
|
|
||||||
current_omega = new_orientation(delta, eta, size=chunks)
|
|
||||||
|
|
||||||
# time to next jump
|
|
||||||
t_wait = new_tau(size=chunks)
|
|
||||||
accumulated_phase = np.cumsum(t_wait*current_omega) + phase[-1]
|
|
||||||
|
|
||||||
t_wait = np.cumsum(t_wait) + t_passed
|
|
||||||
t_passed = t_wait[-1]
|
|
||||||
t.extend(t_wait.tolist())
|
|
||||||
|
|
||||||
phase.extend(accumulated_phase.tolist())
|
|
||||||
|
|
||||||
# convenient interpolation to get phase at arbitrary times
|
|
||||||
phase_interpol = interp1d(t, phase)
|
|
||||||
|
|
||||||
for j, t_echo_j in enumerate(t_echo):
|
|
||||||
# effect of de-phasing and re-phasing
|
|
||||||
start_amp = -2 * phase_interpol(t_echo_j)
|
|
||||||
|
|
||||||
# start of actual acquisition
|
|
||||||
timesignal[:, j] += np.cos(start_amp + phase_interpol(t_acq + 2*t_echo_j)) * dampening
|
|
||||||
|
|
||||||
if (i+1) % 200 == 0:
|
|
||||||
elapsed = time()-start
|
|
||||||
print(f'Step {i+1} of {num_traj}', end=' - ')
|
|
||||||
total = num_traj * elapsed / (i+1)
|
|
||||||
print(f'elapsed: {elapsed:.2f}s - total: {total:.2f}s - remaining: {total-elapsed:.2f}s')
|
|
||||||
|
|
||||||
timesignal /= num_traj
|
|
||||||
|
|
||||||
# FT to spectrum
|
|
||||||
freq = np.fft.fftshift(np.fft.fftfreq(acq_length, dt))
|
|
||||||
spec = np.fft.fftshift(np.fft.fft(timesignal, axis=0), axes=0).real
|
|
||||||
spec -= spec[0]
|
|
||||||
# spec /= np.max(spec, axis=0)
|
|
||||||
|
|
||||||
t_echo_strings = list(map(str, t_echo))
|
|
||||||
|
|
||||||
# plot spectra
|
|
||||||
fig, ax = plt.subplots()
|
|
||||||
lines = ax.plot(freq, spec)
|
|
||||||
ax.set_title(f'RJ (tau = {tau_i}s)')
|
|
||||||
ax.legend(lines, t_echo_strings)
|
|
||||||
# plt.savefig(f'RJ_{tau_i}.png')
|
|
||||||
|
|
||||||
# # save time signals and spectra
|
|
||||||
# np.savetxt(f'rj_spectrum_{tau_i}_chunky.dat', np.c_[freq, spec], header='f\t' + '\t'.join(t_echo_strings))
|
|
||||||
# np.savetxt(f'rj_timesignal_{tau_i}_chunky.dat', np.c_[t_acq, timesignal], header='t\t' + '\t'.join(t_echo_strings))
|
|
||||||
|
|
||||||
plt.show()
|
|
39
src/config.json
Normal file
39
src/config.json
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"simulation": {
|
||||||
|
"num_walker": 2500,
|
||||||
|
"seed": null
|
||||||
|
},
|
||||||
|
"molecule": {
|
||||||
|
"delta": 161e3,
|
||||||
|
"eta": 0.0
|
||||||
|
},
|
||||||
|
"correlation_times": {
|
||||||
|
"distribution": "DeltaDistribution",
|
||||||
|
"tau": {
|
||||||
|
"start": 1e-4,
|
||||||
|
"stop": 1e-2,
|
||||||
|
"steps": 6,
|
||||||
|
"is_log": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"motion": {
|
||||||
|
"model": "RandomJump"
|
||||||
|
},
|
||||||
|
"spectrum": {
|
||||||
|
"dwell_time": 1e-6,
|
||||||
|
"num_points": 4096,
|
||||||
|
"t_echo": {
|
||||||
|
"list": [0, 5e-6, 10e-6, 20e-6, 50e-6, 100e-6, 200e-6]
|
||||||
|
},
|
||||||
|
"line_broadening": 2e3
|
||||||
|
},
|
||||||
|
"stimulated_echo": {
|
||||||
|
"t_evo": 10e-6,
|
||||||
|
"t_mix": {
|
||||||
|
"start": 1e-5,
|
||||||
|
"stop": 1e-2,
|
||||||
|
"steps": 10,
|
||||||
|
"is_log": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
src/config_ste.json
Normal file
30
src/config_ste.json
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"simulation": {
|
||||||
|
"num_walker": 20000,
|
||||||
|
"seed": null
|
||||||
|
},
|
||||||
|
"molecule": {
|
||||||
|
"delta": 161e3,
|
||||||
|
"eta": 0.0
|
||||||
|
},
|
||||||
|
"correlation_times": {
|
||||||
|
"distribution": "DeltaDistribution",
|
||||||
|
"tau": 1e-2
|
||||||
|
},
|
||||||
|
"motion": {
|
||||||
|
"model": "RandomJump"
|
||||||
|
},
|
||||||
|
"stimulated_echo": {
|
||||||
|
"t_evo": {
|
||||||
|
"start": 1e-6,
|
||||||
|
"stop": 40e-6,
|
||||||
|
"steps": 80
|
||||||
|
},
|
||||||
|
"t_mix": {
|
||||||
|
"start": 1e-5,
|
||||||
|
"stop": 1e0,
|
||||||
|
"steps": 21,
|
||||||
|
"is_log": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
0
src/rwsims/__init__.py
Normal file
0
src/rwsims/__init__.py
Normal file
16
src/rwsims/distributions.py
Normal file
16
src/rwsims/distributions.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from numpy.typing import ArrayLike
|
||||||
|
from numpy.random import Generator
|
||||||
|
|
||||||
|
|
||||||
|
class DeltaDistribution:
|
||||||
|
def __init__(self, tau: float, rng: Generator | None = None):
|
||||||
|
self._tau = tau
|
||||||
|
self._rng = rng
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'DeltaDistribution (tau={self._tau})'
|
||||||
|
|
||||||
|
def wait(self, size: int = 1) -> ArrayLike:
|
||||||
|
return self._rng.exponential(self._tau, size=size)
|
33
src/rwsims/motions.py
Normal file
33
src/rwsims/motions.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from numpy.random import Generator
|
||||||
|
from numpy.typing import ArrayLike
|
||||||
|
|
||||||
|
|
||||||
|
def omega_q(delta: float, eta: float, theta: float, phi: float) -> ArrayLike:
|
||||||
|
cos_theta = np.cos(theta)
|
||||||
|
sin_theta = np.sin(theta)
|
||||||
|
return 2 * np.pi * delta * (3 * cos_theta * cos_theta - 1 + eta * sin_theta*sin_theta * np.cos(2*phi))
|
||||||
|
|
||||||
|
|
||||||
|
def draw_orientation(delta: float, eta: float, rng: Generator, size: int = 1) -> ArrayLike:
|
||||||
|
z_theta, z_phi = rng.random((2, size))
|
||||||
|
theta = np.arccos(1 - 2 * z_theta)
|
||||||
|
phi = 2 * np.pi * z_phi
|
||||||
|
|
||||||
|
return omega_q(delta, eta, theta, phi)
|
||||||
|
|
||||||
|
|
||||||
|
class RandomJump:
|
||||||
|
def __init__(self, delta: float, eta: float, rng: Generator | None = None):
|
||||||
|
self._delta = delta
|
||||||
|
self._eta = eta
|
||||||
|
|
||||||
|
self._rng = rng
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'Random Jump'
|
||||||
|
|
||||||
|
def jump(self, size: int = 1) -> ArrayLike:
|
||||||
|
return draw_orientation(self._delta, self._eta, self._rng, size=size)
|
108
src/rwsims/parameter.py
Normal file
108
src/rwsims/parameter.py
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from itertools import product
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from src.rwsims.distributions import DeltaDistribution
|
||||||
|
from src.rwsims.motions import RandomJump
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['SimParameter', 'MoleculeParameter', 'StimEchoParameter', 'SpectrumParameter', 'DistParameter', 'MotionParameter', 'Parameter']
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SimParameter:
|
||||||
|
seed: int | None
|
||||||
|
num_walker: int
|
||||||
|
t_max: float
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MoleculeParameter:
|
||||||
|
delta: float
|
||||||
|
eta: float
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class StimEchoParameter:
|
||||||
|
t_evo: np.ndarray
|
||||||
|
t_mix: np.ndarray
|
||||||
|
t_max: float = field(init=False)
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
self.t_max = np.max(self.t_mix) + 2 * np.max(self.t_evo)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SpectrumParameter:
|
||||||
|
dwell_time: float
|
||||||
|
num_points: int
|
||||||
|
t_echo: np.ndarray
|
||||||
|
t_acq: np.ndarray = field(init=False)
|
||||||
|
t_max: float = field(init=False)
|
||||||
|
lb: float
|
||||||
|
dampening: np.ndarray = field(init=False)
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
self.t_acq = np.arange(self.num_points) * self.dwell_time
|
||||||
|
self.dampening = np.exp(-self.lb * self.t_acq)
|
||||||
|
self.t_max = np.max(self.t_acq) + 2 * np.max(self.t_echo)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DistParameter:
|
||||||
|
dist_type: DeltaDistribution
|
||||||
|
variables: field(default_factory=dict)
|
||||||
|
num_variables: int = 0
|
||||||
|
iter: field(init=False) = None
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
self.num_variables = sum(map(len, self.variables.values()))
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __next__(self) -> dict[str, Any]:
|
||||||
|
if self.iter is None:
|
||||||
|
self.iter = product(*self.variables.values())
|
||||||
|
try:
|
||||||
|
return dict(zip(self.variables.keys(), next(self.iter)))
|
||||||
|
except StopIteration:
|
||||||
|
self.iter = None
|
||||||
|
raise StopIteration
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MotionParameter:
|
||||||
|
model: RandomJump
|
||||||
|
variables: field(default_factory=dict)
|
||||||
|
num_variables: int = 0
|
||||||
|
iter: field(init=False) = None
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
self.num_variables = sum(map(len, self.variables.values()))
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __next__(self) -> dict[str, Any]:
|
||||||
|
if self.iter is None:
|
||||||
|
self.iter = product(*self.variables.values())
|
||||||
|
try:
|
||||||
|
return dict(zip(self.variables.keys(), next(self.iter)))
|
||||||
|
except StopIteration:
|
||||||
|
self.iter = None
|
||||||
|
raise StopIteration
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Parameter:
|
||||||
|
ste: StimEchoParameter | None
|
||||||
|
spec: SpectrumParameter | None
|
||||||
|
sim: SimParameter
|
||||||
|
dist: DistParameter
|
||||||
|
motion: MotionParameter
|
||||||
|
molecule: MoleculeParameter
|
130
src/rwsims/parser.py
Normal file
130
src/rwsims/parser.py
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from distributions import (
|
||||||
|
DeltaDistribution
|
||||||
|
)
|
||||||
|
from motions import RandomJump
|
||||||
|
from parameter import *
|
||||||
|
|
||||||
|
|
||||||
|
def parse(config_file: str) -> Parameter:
|
||||||
|
with open(config_file, 'r') as f:
|
||||||
|
parameter: dict = json.load(f)
|
||||||
|
|
||||||
|
ste = _parse_ste(parameter.get('stimulated_echo'))
|
||||||
|
spec = _parse_spectrum(parameter.get('spectrum'))
|
||||||
|
|
||||||
|
if ste is None and spec is None:
|
||||||
|
raise ValueError("No parameter for STE or spectra given")
|
||||||
|
|
||||||
|
t_max = 0
|
||||||
|
if spec is not None:
|
||||||
|
t_max = max(spec.t_max, t_max)
|
||||||
|
if ste is not None:
|
||||||
|
t_max = max(ste.t_max, t_max)
|
||||||
|
parameter['simulation'].update({'t_max': t_max})
|
||||||
|
|
||||||
|
sim = _parse_sim(parameter['simulation'])
|
||||||
|
dist = _parse_dist(parameter['correlation_times'])
|
||||||
|
motion = _parse_motion(parameter['motion'])
|
||||||
|
mol = _parse_molecule(parameter['molecule'])
|
||||||
|
|
||||||
|
p = Parameter(sim=sim, ste=ste, spec=spec, dist=dist, motion=motion, molecule=mol)
|
||||||
|
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_sim(params: dict[str, Any]) -> SimParameter:
|
||||||
|
sim = SimParameter(
|
||||||
|
num_walker=params['num_walker'],
|
||||||
|
seed=params['seed'],
|
||||||
|
t_max=params['t_max']
|
||||||
|
)
|
||||||
|
return sim
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_ste(params: dict[str, Any] | None) -> StimEchoParameter | None:
|
||||||
|
if params is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
ste = StimEchoParameter(
|
||||||
|
t_mix=_make_times(params['t_mix']),
|
||||||
|
t_evo=_make_times(params['t_evo']),
|
||||||
|
)
|
||||||
|
return ste
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_spectrum(params: dict[str, Any] | None) -> SpectrumParameter | None:
|
||||||
|
if params is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
spec = SpectrumParameter(
|
||||||
|
num_points=params['num_points'],
|
||||||
|
dwell_time=params['dwell_time'],
|
||||||
|
t_echo=_make_times(params['t_echo']),
|
||||||
|
lb=params['line_broadening']
|
||||||
|
)
|
||||||
|
|
||||||
|
return spec
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_dist(params: dict[str, Any]) -> DistParameter:
|
||||||
|
mapping: dict = {
|
||||||
|
'DeltaDistribution': DeltaDistribution
|
||||||
|
}
|
||||||
|
p = DistParameter(
|
||||||
|
dist_type=mapping[params['distribution']],
|
||||||
|
variables={k: _make_times(v) for k, v in params.items() if k != 'distribution'},
|
||||||
|
)
|
||||||
|
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_motion(params: dict[str, Any]) -> MotionParameter:
|
||||||
|
mapping = {
|
||||||
|
'RandomJump': RandomJump,
|
||||||
|
}
|
||||||
|
|
||||||
|
p = MotionParameter(
|
||||||
|
model=mapping[params['model']],
|
||||||
|
variables={k: _make_times(v) for k, v in params.items() if k != 'model'}
|
||||||
|
)
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_molecule(params: dict[str, Any]) -> MoleculeParameter:
|
||||||
|
return MoleculeParameter(
|
||||||
|
delta=params['delta'],
|
||||||
|
eta=params['eta']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _make_times(params: float | int | dict[str, Any]) -> np.ndarray:
|
||||||
|
times = None
|
||||||
|
|
||||||
|
if isinstance(params, (int, float, complex)):
|
||||||
|
times = np.array([params])
|
||||||
|
|
||||||
|
else:
|
||||||
|
if all(k in params for k in ('start', 'stop', 'steps')):
|
||||||
|
space_func = np.linspace
|
||||||
|
if 'is_log' in params and params['is_log']:
|
||||||
|
space_func = np.geomspace
|
||||||
|
|
||||||
|
times = space_func(start=params['start'], stop=params['stop'], num=params['steps'])
|
||||||
|
|
||||||
|
if 'list' in params:
|
||||||
|
if times is not None:
|
||||||
|
raise ValueError('list and range is given')
|
||||||
|
|
||||||
|
times = np.array(params['list'])
|
||||||
|
|
||||||
|
if times is None:
|
||||||
|
raise ValueError('No times are given')
|
||||||
|
|
||||||
|
return times
|
212
src/rwsims/sims.py
Normal file
212
src/rwsims/sims.py
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from time import time
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from numpy.random import Generator
|
||||||
|
from scipy.interpolate import interp1d
|
||||||
|
from scipy.optimize import curve_fit
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
from parameter import Parameter
|
||||||
|
from parser import parse
|
||||||
|
|
||||||
|
|
||||||
|
def ste(x, a, f_infty, tau, beta):
|
||||||
|
return a*((1-f_infty) * np.exp(-(x/tau)**beta) + f_infty)
|
||||||
|
|
||||||
|
|
||||||
|
def run_spectrum_sim(config_file: str):
|
||||||
|
p = parse(config_file)
|
||||||
|
|
||||||
|
rng, num_traj, t_max, delta, eta, num_variables = _prepare_sim(p)
|
||||||
|
|
||||||
|
num_echos = len(p.spec.t_echo)
|
||||||
|
reduction_factor = np.zeros((num_variables, num_echos))
|
||||||
|
freq = np.fft.fftshift(np.fft.fftfreq(p.spec.num_points, p.spec.dwell_time))
|
||||||
|
t_echo = p.spec.t_echo
|
||||||
|
t_echo_strings = list(map(str, t_echo))
|
||||||
|
|
||||||
|
# outer loop over variables of distribution of correlation times
|
||||||
|
for (i, dist_values) in enumerate(p.dist):
|
||||||
|
# noinspection PyCallingNonCallable
|
||||||
|
dist = p.dist.dist_type(**dist_values, rng=rng)
|
||||||
|
print(f'\nStart of {dist}')
|
||||||
|
|
||||||
|
chunks = int(0.6 * t_max / dist_values.get('tau', 1)) + 1
|
||||||
|
|
||||||
|
# second loop over parameter of motional model
|
||||||
|
for (j, motion_values) in enumerate(p.motion):
|
||||||
|
# noinspection PyCallingNonCallable
|
||||||
|
motion = p.motion.model(delta, eta, **motion_values, rng=rng)
|
||||||
|
print(f'Start of {motion}')
|
||||||
|
|
||||||
|
print(f'Simulate in chunks of {chunks}')
|
||||||
|
|
||||||
|
timesignal = np.zeros((p.spec.num_points, num_echos))
|
||||||
|
|
||||||
|
start = time()
|
||||||
|
|
||||||
|
# inner loop to create trajectories
|
||||||
|
for n in range(num_traj):
|
||||||
|
phase_interpol = make_trajectory(chunks, dist, motion, t_max)
|
||||||
|
|
||||||
|
for (k, t_echo_k) in enumerate(t_echo):
|
||||||
|
# effect of de-phasing and re-phasing
|
||||||
|
start_amp = -2 * phase_interpol(t_echo_k)
|
||||||
|
|
||||||
|
# start of actual acquisition
|
||||||
|
timesignal[:, k] += np.cos(start_amp + phase_interpol(p.spec.t_acq + 2 * t_echo_k)) * p.spec.dampening
|
||||||
|
reduction_factor[max(p.motion.num_variables, 1)*i + j, k] += np.cos(phase_interpol(2 * t_echo_k) + start_amp)
|
||||||
|
|
||||||
|
print_step(n, num_traj, start)
|
||||||
|
|
||||||
|
timesignal /= num_traj
|
||||||
|
|
||||||
|
# FT to spectrum
|
||||||
|
spec = np.fft.fftshift(np.fft.fft(timesignal, axis=0), axes=0).real
|
||||||
|
spec -= spec[0]
|
||||||
|
|
||||||
|
# plot spectra
|
||||||
|
fig, ax = plt.subplots()
|
||||||
|
lines = ax.plot(freq, spec)
|
||||||
|
ax.set_title(f'{dist}, {motion}')
|
||||||
|
ax.legend(lines, t_echo_strings)
|
||||||
|
|
||||||
|
fig2, ax2 = plt.subplots()
|
||||||
|
ax2.semilogx(p.dist.variables['tau'], reduction_factor/num_traj, 'o--')
|
||||||
|
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
|
||||||
|
def run_ste_sim(config_file: str):
|
||||||
|
p = parse(config_file)
|
||||||
|
|
||||||
|
rng, num_traj, t_max, delta, eta, num_variables = _prepare_sim(p)
|
||||||
|
|
||||||
|
cc = np.zeros((len(p.ste.t_mix), num_variables, len(p.ste.t_evo)))
|
||||||
|
ss = np.zeros((len(p.ste.t_mix), num_variables, len(p.ste.t_evo)))
|
||||||
|
|
||||||
|
# outer loop over variables of distribution of correlation times
|
||||||
|
for (i, dist_values) in enumerate(p.dist):
|
||||||
|
# noinspection PyCallingNonCallable
|
||||||
|
dist = p.dist.dist_type(**dist_values, rng=rng)
|
||||||
|
print(f'\nStart of {dist}')
|
||||||
|
|
||||||
|
chunks = int(0.6 * t_max / dist_values.get('tau', 1)) + 1
|
||||||
|
|
||||||
|
# second loop over parameter of motional model
|
||||||
|
for (j, motion_values) in enumerate(p.motion):
|
||||||
|
# noinspection PyCallingNonCallable
|
||||||
|
motion = p.motion.model(delta, eta, **motion_values, rng=rng)
|
||||||
|
|
||||||
|
print(f'Start of {motion}')
|
||||||
|
print(f'Simulate in chunks of {chunks}')
|
||||||
|
|
||||||
|
start = time()
|
||||||
|
|
||||||
|
# inner loop to create trajectories
|
||||||
|
for n in range(num_traj):
|
||||||
|
phase_interpol = make_trajectory(chunks, dist, motion, t_max)
|
||||||
|
|
||||||
|
for (k, t_evo_k) in enumerate(p.ste.t_evo):
|
||||||
|
dephased = phase_interpol(t_evo_k)
|
||||||
|
rephased = phase_interpol(p.ste.t_mix + 2*t_evo_k)-phase_interpol(p.ste.t_mix+t_evo_k)
|
||||||
|
cc[:, max(p.motion.num_variables, 1)*i + j, k] += np.cos(dephased)*np.cos(rephased)
|
||||||
|
ss[:, max(p.motion.num_variables, 1)*i + j, k] += np.sin(dephased)*np.sin(rephased)
|
||||||
|
|
||||||
|
print_step(n, num_traj, start)
|
||||||
|
|
||||||
|
cc /= num_traj
|
||||||
|
ss /= num_traj
|
||||||
|
|
||||||
|
fig, ax = plt.subplots()
|
||||||
|
fig2, ax2 = plt.subplots()
|
||||||
|
fig5, ax5 = plt.subplots()
|
||||||
|
fig3, ax3 = plt.subplots()
|
||||||
|
fig4, ax4 = plt.subplots()
|
||||||
|
|
||||||
|
for j in range(num_variables):
|
||||||
|
p0 = [0.5, 0, 1e-2, 1]
|
||||||
|
|
||||||
|
ax3.plot(p.ste.t_evo, cc[0, j, :])
|
||||||
|
ax3.plot(p.ste.t_evo, ss[0, j, :])
|
||||||
|
ax4.plot(p.ste.t_evo, cc[-1, j, :] / cc[0, j, :])
|
||||||
|
ax4.plot(p.ste.t_evo, ss[-1, j, :] / ss[0, j, :])
|
||||||
|
p_final = []
|
||||||
|
p_final1 = []
|
||||||
|
for k, t_evo_k in enumerate(p.ste.t_evo):
|
||||||
|
res = curve_fit(ste, p.ste.t_mix, cc[:, j, k], p0=p0)
|
||||||
|
res2 = curve_fit(ste, p.ste.t_mix, ss[:, j, k], p0=p0)
|
||||||
|
p_final.append(res[0].tolist())
|
||||||
|
p_final1.append(res2[0].tolist())
|
||||||
|
|
||||||
|
p_final = np.array(p_final)
|
||||||
|
p_final1 = np.array(p_final1)
|
||||||
|
ax.plot(p.ste.t_evo, p_final[:, 0])
|
||||||
|
ax.plot(p.ste.t_evo, p_final1[:, 0])
|
||||||
|
ax.plot(p.ste.t_evo, p_final[:, 1])
|
||||||
|
ax.plot(p.ste.t_evo, p_final1[:, 1])
|
||||||
|
ax5.semilogy(p.ste.t_evo, p_final[:, 2])
|
||||||
|
ax5.semilogy(p.ste.t_evo, p_final1[:, 2])
|
||||||
|
ax2.plot(p.ste.t_evo, p_final[:, 3])
|
||||||
|
ax2.plot(p.ste.t_evo, p_final1[:, 3])
|
||||||
|
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
|
||||||
|
def print_step(n, num_traj, start):
|
||||||
|
if (n + 1) % 200 == 0:
|
||||||
|
elapsed = time() - start
|
||||||
|
print(f'Step {n + 1} of {num_traj}', end=' - ')
|
||||||
|
total = num_traj * elapsed / (n + 1)
|
||||||
|
print(f'total: {total:.2f}s - elapsed: {elapsed:.2f}s - remaining: {total - elapsed:.2f}s')
|
||||||
|
|
||||||
|
|
||||||
|
def make_trajectory(chunks: int, dist, motion, t_max: float):
|
||||||
|
t_passed = 0
|
||||||
|
t = [0]
|
||||||
|
phase = [0]
|
||||||
|
accumulated_phase = 0
|
||||||
|
while t_passed < t_max:
|
||||||
|
# orientation until the next jump
|
||||||
|
current_omega = motion.jump(size=chunks)
|
||||||
|
|
||||||
|
# time to next jump
|
||||||
|
t_wait = dist.wait(size=chunks)
|
||||||
|
|
||||||
|
accumulated_phase = np.cumsum(t_wait * current_omega) + phase[-1]
|
||||||
|
|
||||||
|
t_wait = np.cumsum(t_wait) + t_passed
|
||||||
|
t_passed = t_wait[-1]
|
||||||
|
t.extend(t_wait.tolist())
|
||||||
|
|
||||||
|
phase.extend(accumulated_phase.tolist())
|
||||||
|
|
||||||
|
# convenient interpolation to get phase at arbitrary times
|
||||||
|
phase_interpol = interp1d(t, phase)
|
||||||
|
|
||||||
|
return phase_interpol
|
||||||
|
|
||||||
|
|
||||||
|
def _prepare_sim(parameter: Parameter) -> tuple[Generator, int, float, float, float, int]:
|
||||||
|
# random number generator
|
||||||
|
rng = np.random.default_rng(parameter.sim.seed)
|
||||||
|
|
||||||
|
# number of random walkers
|
||||||
|
num_traj = parameter.sim.num_walker
|
||||||
|
|
||||||
|
# length of trajectories
|
||||||
|
t_max = parameter.sim.t_max
|
||||||
|
|
||||||
|
# parameter for omega_q
|
||||||
|
delta, eta = parameter.molecule.delta, parameter.molecule.eta
|
||||||
|
|
||||||
|
num_variables = parameter.dist.num_variables + parameter.motion.num_variables
|
||||||
|
|
||||||
|
return rng, num_traj, t_max, delta, eta, num_variables
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
run_ste_sim('../config.json')
|
||||||
|
run_spectrum_sim('../config.json')
|
@ -11,12 +11,12 @@ eta = 0
|
|||||||
lb = 5e3 # in Hz
|
lb = 5e3 # in Hz
|
||||||
|
|
||||||
# correlation time
|
# correlation time
|
||||||
tau = [1e-6] # in s
|
tau = np.logspace(-8, -1, num=15) # in s
|
||||||
|
|
||||||
# acquisition parameter
|
# acquisition parameter
|
||||||
acq_length = 4096
|
acq_length = 4096
|
||||||
dt = 1e-6 # in s
|
dt = 1e-6 # in s
|
||||||
t_echo = [0, 5e-6, 10e-6, 20e-6, 50e-6, 100e-6, 200e-6] # all in s
|
t_echo = [5e-6, 10e-6, 20e-6, 50e-6, 100e-6, 200e-6] # all in s
|
||||||
|
|
||||||
# derived parameter
|
# derived parameter
|
||||||
t_acq = np.arange(acq_length) * dt
|
t_acq = np.arange(acq_length) * dt
|
||||||
@ -28,7 +28,7 @@ seed = None
|
|||||||
rng = np.random.default_rng(seed)
|
rng = np.random.default_rng(seed)
|
||||||
|
|
||||||
# number of random walkers
|
# number of random walkers
|
||||||
num_traj = 50000
|
num_traj = 1
|
||||||
|
|
||||||
|
|
||||||
def omega_q(delta_: float, eta_: float, theta_: ArrayLike, phi_: ArrayLike) -> np.ndarray:
|
def omega_q(delta_: float, eta_: float, theta_: ArrayLike, phi_: ArrayLike) -> np.ndarray:
|
||||||
@ -60,7 +60,10 @@ def new_tau(size=1) -> np.ndarray:
|
|||||||
return rng.exponential(tau_i, size=size)
|
return rng.exponential(tau_i, size=size)
|
||||||
|
|
||||||
|
|
||||||
for tau_i in tau:
|
reduction_factor = np.zeros((len(tau), len(t_echo)))
|
||||||
|
|
||||||
|
|
||||||
|
for (n, tau_i) in enumerate(tau):
|
||||||
print(f'\nStart for tau={tau_i}')
|
print(f'\nStart for tau={tau_i}')
|
||||||
|
|
||||||
timesignal = np.zeros((acq_length, len(t_echo)))
|
timesignal = np.zeros((acq_length, len(t_echo)))
|
||||||
@ -127,6 +130,7 @@ for tau_i in tau:
|
|||||||
|
|
||||||
# start of actual acquisition
|
# start of actual acquisition
|
||||||
timesignal[:, j] += np.cos(start_amp + phase_interpol(t_acq + 2*t_echo_j)) * dampening
|
timesignal[:, j] += np.cos(start_amp + phase_interpol(t_acq + 2*t_echo_j)) * dampening
|
||||||
|
reduction_factor[n, j] += np.cos(phase_interpol(2*t_echo_j) + start_amp)
|
||||||
|
|
||||||
if (i+1) % 200 == 0:
|
if (i+1) % 200 == 0:
|
||||||
elapsed = time()-start
|
elapsed = time()-start
|
||||||
@ -155,4 +159,7 @@ for tau_i in tau:
|
|||||||
# np.savetxt(f'spectrum_{tau_i}.dat', np.c_[freq, spec], header='f\t' + '\t'.join(t_echo_strings))
|
# np.savetxt(f'spectrum_{tau_i}.dat', np.c_[freq, spec], header='f\t' + '\t'.join(t_echo_strings))
|
||||||
# np.savetxt(f'timesignal_{tau_i}.dat', np.c_[t_acq, timesignal], header='t\t' + '\t'.join(t_echo_strings))
|
# np.savetxt(f'timesignal_{tau_i}.dat', np.c_[t_acq, timesignal], header='t\t' + '\t'.join(t_echo_strings))
|
||||||
|
|
||||||
|
fig2, ax2 = plt.subplots()
|
||||||
|
ax2.semilogx(tau, reduction_factor / num_traj, 'o--')
|
||||||
|
|
||||||
plt.show()
|
plt.show()
|
Loading…
Reference in New Issue
Block a user