Moved functions from mdeval_skloth and removed deprecated code
This commit is contained in:
parent
4b139db471
commit
1aa5eed106
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "mdevaluate"
|
name = "mdevaluate"
|
||||||
version = "23.7"
|
version = "23.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"mdanalysis",
|
"mdanalysis",
|
||||||
"pandas",
|
"pandas",
|
||||||
|
@ -11,6 +11,8 @@ from . import functions
|
|||||||
from . import pbc
|
from . import pbc
|
||||||
from . import autosave
|
from . import autosave
|
||||||
from . import reader
|
from . import reader
|
||||||
|
from . import chill
|
||||||
|
from . import system
|
||||||
from .logging import logger
|
from .logging import logger
|
||||||
|
|
||||||
|
|
||||||
|
79
src/mdevaluate/chill.py
Normal file
79
src/mdevaluate/chill.py
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import numpy as np
|
||||||
|
from scipy.spatial import KDTree
|
||||||
|
from scipy.special import sph_harm
|
||||||
|
|
||||||
|
|
||||||
|
def a_ij(atoms, N=4, l=3):
|
||||||
|
tree = KDTree(atoms)
|
||||||
|
|
||||||
|
dist, indices = tree.query(atoms, N + 1)
|
||||||
|
indices = indices[:, 1:]
|
||||||
|
|
||||||
|
vecs = atoms[:, np.newaxis, :] - atoms[indices]
|
||||||
|
vecs /= np.linalg.norm(vecs, axis=-1)[..., np.newaxis]
|
||||||
|
|
||||||
|
theta = np.arctan2(vecs[..., 1], vecs[..., 0]) + np.pi
|
||||||
|
phi = np.arccos(vecs[..., 2])
|
||||||
|
qijlm = sph_harm(
|
||||||
|
np.arange(-l, l + 1)[np.newaxis, np.newaxis, :],
|
||||||
|
l,
|
||||||
|
theta[..., np.newaxis],
|
||||||
|
phi[..., np.newaxis],
|
||||||
|
)
|
||||||
|
qilm = np.average(qijlm, axis=1)
|
||||||
|
qil = np.sum(qilm * np.conj(qilm), axis=-1) ** 0.5
|
||||||
|
aij = (
|
||||||
|
np.sum(qilm[:, np.newaxis, :] * np.conj(qilm[indices]), axis=-1)
|
||||||
|
/ qil[:, np.newaxis]
|
||||||
|
/ qil[indices]
|
||||||
|
)
|
||||||
|
return np.real(aij), indices
|
||||||
|
|
||||||
|
|
||||||
|
def number_of_neighbors(atoms):
|
||||||
|
tree = KDTree(atoms)
|
||||||
|
dist, _ = tree.query(atoms, 10, distance_upper_bound=0.35)
|
||||||
|
return np.array([len(distance[distance < 0.4]) - 1 for distance in dist])
|
||||||
|
|
||||||
|
|
||||||
|
def classify_ice(aij, indices, neighbors, indexSOL):
|
||||||
|
staggerdBonds = np.sum(aij <= -0.8, axis=1)
|
||||||
|
eclipsedBonds = np.sum((aij >= -0.35) & (aij <= 0.25), axis=1)
|
||||||
|
|
||||||
|
iceTypes = np.full(len(aij), 5)
|
||||||
|
for i in indexSOL:
|
||||||
|
if neighbors[i] != 4:
|
||||||
|
continue
|
||||||
|
elif staggerdBonds[i] == 4:
|
||||||
|
iceTypes[i] = 0
|
||||||
|
elif staggerdBonds[i] == 3 and eclipsedBonds[i] == 1:
|
||||||
|
iceTypes[i] = 1
|
||||||
|
elif staggerdBonds[i] == 3:
|
||||||
|
for j in indices[i]:
|
||||||
|
if staggerdBonds[j] >= 2:
|
||||||
|
iceTypes[i] = 2
|
||||||
|
break
|
||||||
|
elif staggerdBonds[i] == 2:
|
||||||
|
for j in indices[i]:
|
||||||
|
if staggerdBonds[j] >= 3:
|
||||||
|
iceTypes[i] = 2
|
||||||
|
break
|
||||||
|
elif eclipsedBonds[i] == 4:
|
||||||
|
iceTypes[i] = 3
|
||||||
|
elif eclipsedBonds[i] == 3:
|
||||||
|
iceTypes[i] = 4
|
||||||
|
iceTypes = iceTypes[indexSOL]
|
||||||
|
|
||||||
|
return iceTypes
|
||||||
|
|
||||||
|
|
||||||
|
def ice_parts(iceTypes):
|
||||||
|
cubic = len(iceTypes[iceTypes == 0])
|
||||||
|
hexagonal = len(iceTypes[iceTypes == 1])
|
||||||
|
interface = len(iceTypes[iceTypes == 2])
|
||||||
|
clathrate = len(iceTypes[iceTypes == 3])
|
||||||
|
clathrate_interface = len(iceTypes[iceTypes == 4])
|
||||||
|
liquid = len(iceTypes[iceTypes == 5])
|
||||||
|
return np.array(
|
||||||
|
[cubic, hexagonal, interface, clathrate, clathrate_interface, liquid]
|
||||||
|
)
|
@ -1,37 +0,0 @@
|
|||||||
import argparse
|
|
||||||
from . import logging
|
|
||||||
from . import open as md_open
|
|
||||||
|
|
||||||
|
|
||||||
def run(*args, **kwargs):
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser.add_argument(
|
|
||||||
"xtcfile",
|
|
||||||
help="The xtc file to index.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--tpr", help="The tprfile of the trajectory.", dest="tpr", default=None
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--nojump",
|
|
||||||
help="Generate Nojump Matrices, requires a tpr file.",
|
|
||||||
dest="nojump",
|
|
||||||
action="store_true",
|
|
||||||
default=False,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--debug",
|
|
||||||
help="Set logging level to debug.",
|
|
||||||
dest="debug",
|
|
||||||
action="store_true",
|
|
||||||
default=False,
|
|
||||||
)
|
|
||||||
args = parser.parse_args()
|
|
||||||
if args.debug:
|
|
||||||
logging.setlevel("DEBUG")
|
|
||||||
|
|
||||||
md_open("", trajectory=args.xtcfile, topology=args.tpr, nojump=args.nojump)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
run()
|
|
@ -339,23 +339,6 @@ class Coordinates:
|
|||||||
self.atom_subset.description = desc
|
self.atom_subset.description = desc
|
||||||
|
|
||||||
|
|
||||||
class MeanCoordinates(Coordinates):
|
|
||||||
def __init__(self, frames, atom_filter=None, mean=1):
|
|
||||||
super().__init__(frames, atom_filter)
|
|
||||||
self.mean = mean
|
|
||||||
assert mean >= 1, "Mean must be positive"
|
|
||||||
|
|
||||||
def __getitem__(self, item):
|
|
||||||
frame = super().__getitem__(item)
|
|
||||||
for i in range(item + 1, item + self.mean):
|
|
||||||
frame += super().__getitem__(i)
|
|
||||||
|
|
||||||
return frame / self.mean
|
|
||||||
|
|
||||||
def len(self):
|
|
||||||
return len(super() - self.mean + 1)
|
|
||||||
|
|
||||||
|
|
||||||
class CoordinatesMap:
|
class CoordinatesMap:
|
||||||
def __init__(self, coordinates, function):
|
def __init__(self, coordinates, function):
|
||||||
self.coordinates = coordinates
|
self.coordinates = coordinates
|
||||||
@ -419,74 +402,6 @@ class CoordinatesMap:
|
|||||||
return CoordinatesMap(self.coordinates.pbc, self.function)
|
return CoordinatesMap(self.coordinates.pbc, self.function)
|
||||||
|
|
||||||
|
|
||||||
class CoordinatesFilter:
|
|
||||||
@property
|
|
||||||
def atom_subset(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __init__(self, coordinates, atom_filter):
|
|
||||||
self.coordinates = coordinates
|
|
||||||
self.atom_filter = atom_filter
|
|
||||||
|
|
||||||
def __getitem__(self, item):
|
|
||||||
if isinstance(item, slice):
|
|
||||||
sliced = copy(self)
|
|
||||||
sliced.coordinates = self.coordinates[item]
|
|
||||||
return sliced
|
|
||||||
else:
|
|
||||||
frame = self.coordinates[item]
|
|
||||||
return frame[self.atom_filter]
|
|
||||||
|
|
||||||
|
|
||||||
class CoordinatesKDTree:
|
|
||||||
"""
|
|
||||||
A KDTree of coordinates frames. The KDtrees are cached by a :func:`functools.lru_cache`.
|
|
||||||
Uses :class:`scipy.spatial.cKDTree` by default, since it's significantly faster.
|
|
||||||
Make sure to use scipy 0.17 or later or switch to the normal KDTree, since cKDTree has
|
|
||||||
a memory leak in earlier versions.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def clear_cache(self):
|
|
||||||
"""Clear the LRU cache."""
|
|
||||||
self._get_tree_at_index.cache_clear()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def cache_info(self):
|
|
||||||
"""Return info about the state of the cache."""
|
|
||||||
return self._get_tree_at_index.cache_info()
|
|
||||||
|
|
||||||
def _get_tree_at_index(self, index):
|
|
||||||
frame = self.frames[index]
|
|
||||||
return self.kdtree(frame[self.selector(frame)])
|
|
||||||
|
|
||||||
def __init__(self, frames, selector=None, boxsize=None, maxcache=128, ckdtree=True):
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
frames: Trajectory of the simulation, can be Coordinates object or reader
|
|
||||||
selector: Selector function that selects a subset of each frame
|
|
||||||
maxcache: Maxsize of the :func:`~functools.lru_cache`
|
|
||||||
ckdtree: Use :class:`~scipy.spatial.cKDTree` or :class:`~scipy.spatial.KDTree` if False
|
|
||||||
"""
|
|
||||||
if selector is not None:
|
|
||||||
self.selector = selector
|
|
||||||
else:
|
|
||||||
self.selector = lambda x: slice(None)
|
|
||||||
self.frames = frames
|
|
||||||
self.kdtree = cKDTree if ckdtree else KDTree
|
|
||||||
if boxsize is not None:
|
|
||||||
self.kdtree = partial(self.kdtree, boxsize=boxsize)
|
|
||||||
self._get_tree_at_index = lru_cache(maxsize=maxcache)(self._get_tree_at_index)
|
|
||||||
|
|
||||||
def __getitem__(self, index):
|
|
||||||
return self._get_tree_at_index(index)
|
|
||||||
|
|
||||||
def __checksum__(self):
|
|
||||||
return checksum(self.selector, self.frames)
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return super().__eq__(other)
|
|
||||||
|
|
||||||
|
|
||||||
def map_coordinates(func):
|
def map_coordinates(func):
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def wrapped(coordinates, **kwargs):
|
def wrapped(coordinates, **kwargs):
|
||||||
@ -496,38 +411,35 @@ def map_coordinates(func):
|
|||||||
|
|
||||||
|
|
||||||
@map_coordinates
|
@map_coordinates
|
||||||
def centers_of_mass(c, *, masses=None):
|
def center_of_masses(coordinates, atoms, shear: bool = False):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
A- 1
|
|
||||||
B- 2
|
|
||||||
A- 1
|
|
||||||
C 3
|
|
||||||
A-
|
|
||||||
B-
|
|
||||||
A-
|
|
||||||
C
|
|
||||||
A-
|
|
||||||
B-
|
|
||||||
A-
|
|
||||||
C
|
|
||||||
|
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
rd = XTCReader('t.xtc')
|
rd = XTCReader('t.xtc')
|
||||||
coordinates = Coordinates(rd)
|
coordinates = Coordinates(rd)
|
||||||
com = centers_of_mass(coordinates, (1.0, 2.0, 1.0, 3.0))
|
com = centers_of_mass(coordinates, (1.0, 2.0, 1.0, 3.0))
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# At first, regroup our array
|
res_ids = coordinates.residue_ids[atoms]
|
||||||
number_of_masses = len(masses)
|
masses = coordinates.masses[atoms]
|
||||||
number_of_coordinates, number_of_dimensions = c.shape
|
if shear:
|
||||||
number_of_new_coordinates = number_of_coordinates // number_of_masses
|
coords = coordinates[atoms]
|
||||||
grouped_masses = c.reshape(
|
box = coords.box
|
||||||
number_of_new_coordinates, number_of_masses, number_of_dimensions
|
sort_ind = res_ids.argsort(kind="stable")
|
||||||
)
|
i = np.concatenate([[0], np.where(np.diff(res_ids[sort_ind]) > 0)[0] + 1])
|
||||||
|
coms = coords[sort_ind[i]][res_ids - min(res_ids)]
|
||||||
return np.average(grouped_masses, axis=1, weights=masses)
|
cor = md.pbc.pbc_diff(coords, coms, box)
|
||||||
|
coords = coms + cor
|
||||||
|
else:
|
||||||
|
coords = coordinates.whole[atoms]
|
||||||
|
mask = np.bincount(res_ids)[1:] != 0
|
||||||
|
positions = np.array(
|
||||||
|
[
|
||||||
|
np.bincount(res_ids, weights=c * masses)[1:]
|
||||||
|
/ np.bincount(res_ids, weights=masses)[1:]
|
||||||
|
for c in coords.T
|
||||||
|
]
|
||||||
|
).T[mask]
|
||||||
|
return np.array(positions)
|
||||||
|
|
||||||
|
|
||||||
@map_coordinates
|
@map_coordinates
|
||||||
@ -587,7 +499,83 @@ def vectors(coordinates, atoms_a, atoms_b, normed=False):
|
|||||||
coords_b = coordinates[atoms_b]
|
coords_b = coordinates[atoms_b]
|
||||||
if len(coords_b.shape) > 2:
|
if len(coords_b.shape) > 2:
|
||||||
coords_b = coords_b.mean(axis=0)
|
coords_b = coords_b.mean(axis=0)
|
||||||
vectors = pbc_diff(coords_a, coords_b, box=box)
|
vec = pbc_diff(coords_a, coords_b, box=box)
|
||||||
norm = np.linalg.norm(vectors, axis=-1).reshape(-1, 1) if normed else 1
|
if normed:
|
||||||
vectors.reference = coords_a
|
vec / np.linalg.norm(vec, axis=-1).reshape(-1, 1)
|
||||||
return vectors / norm
|
vec.reference = coords_a
|
||||||
|
return vec
|
||||||
|
|
||||||
|
|
||||||
|
@map_coordinates
|
||||||
|
def dipole_vector(coordinates, atoms, normed=None):
|
||||||
|
coords = coordinates.whole[atoms]
|
||||||
|
res_ids = coordinates.residue_ids[atoms]
|
||||||
|
charges = coordinates.charges[atoms]
|
||||||
|
mask = np.bincount(res_ids)[1:] != 0
|
||||||
|
dipoles = np.array(
|
||||||
|
[np.bincount(res_ids, weights=c * charges)[1:] for c in coords.T]
|
||||||
|
).T[mask]
|
||||||
|
dipoles = np.array(dipoles)
|
||||||
|
if normed:
|
||||||
|
dipoles /= np.linalg.norm(dipoles, axis=-1).reshape(-1, 1)
|
||||||
|
return dipoles
|
||||||
|
|
||||||
|
|
||||||
|
@map_coordinates
|
||||||
|
def sum_dipole_vector(
|
||||||
|
coordinates: CoordinateFrame,
|
||||||
|
atoms: list[int],
|
||||||
|
normed: bool = True,
|
||||||
|
):
|
||||||
|
coords = coordinates.whole[atoms]
|
||||||
|
charges = coordinates.charges[atoms]
|
||||||
|
dipole = np.array([c * charges for c in coords.T]).T
|
||||||
|
if normed:
|
||||||
|
dipole /= np.linalg.norm(dipole)
|
||||||
|
return dipole
|
||||||
|
|
||||||
|
|
||||||
|
@map_coordinates
|
||||||
|
def normal_vectors(
|
||||||
|
coordinates: CoordinateFrame,
|
||||||
|
atoms_a: list[int],
|
||||||
|
atoms_b: list[int],
|
||||||
|
atoms_c: list[int],
|
||||||
|
normed: bool = True,
|
||||||
|
):
|
||||||
|
coords_a = coordinates[atoms_a]
|
||||||
|
coords_b = coordinates[atoms_b]
|
||||||
|
coords_c = coordinates[atoms_c]
|
||||||
|
box = coordinates.box
|
||||||
|
vectors_a = pbc_diff(coords_a, coords_b, box=box)
|
||||||
|
vectors_b = pbc_diff(coords_a, coords_c, box=box)
|
||||||
|
vec = np.cross(vectors_a, vectors_b)
|
||||||
|
if normed:
|
||||||
|
vec /= np.linalg.norm(vec, axis=-1).reshape(-1, 1)
|
||||||
|
vec.reference = coords_a
|
||||||
|
return vec
|
||||||
|
|
||||||
|
|
||||||
|
def displacements_without_drift(
|
||||||
|
start_frame: CoordinateFrame, end_frame: CoordinateFrame, trajectory: Coordinates
|
||||||
|
) -> np.array:
|
||||||
|
start_all = trajectory[start_frame.step]
|
||||||
|
frame_all = trajectory[end_frame.step]
|
||||||
|
displacements = (
|
||||||
|
start_frame
|
||||||
|
- end_frame
|
||||||
|
- (np.average(start_all, axis=0) - np.average(frame_all, axis=0))
|
||||||
|
)
|
||||||
|
return displacements
|
||||||
|
|
||||||
|
|
||||||
|
@map_coordinates
|
||||||
|
def cylindrical_coordinates(frame, origin=None):
|
||||||
|
if origin is None:
|
||||||
|
origin = np.diag(frame.box) / 2
|
||||||
|
x = frame[:, 0] - origin[0]
|
||||||
|
y = frame[:, 1] - origin[1]
|
||||||
|
z = frame[:, 2]
|
||||||
|
radius = (x**2 + y**2) ** 0.5
|
||||||
|
phi = np.arctan2(y, x)
|
||||||
|
return np.array([radius, phi, z]).T
|
@ -1,18 +1,17 @@
|
|||||||
|
from typing import Callable
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
from numpy.typing import ArrayLike
|
||||||
from scipy.special import legendre
|
from scipy.special import legendre
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
import dask.array as darray
|
import dask.array as darray
|
||||||
from pathos.pools import ProcessPool
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
from scipy.spatial import KDTree
|
||||||
|
|
||||||
from .autosave import autosave_data
|
from .autosave import autosave_data
|
||||||
from .utils import filon_fourier_transformation, coherent_sum, histogram
|
from .utils import coherent_sum, histogram
|
||||||
from .pbc import pbc_diff
|
from .pbc import pbc_diff, pbc_points
|
||||||
|
from .coordinates import Coordinates, CoordinateFrame, displacements_without_drift
|
||||||
|
|
||||||
def set_has_counter(func):
|
|
||||||
func.has_counter = True
|
|
||||||
return func
|
|
||||||
|
|
||||||
|
|
||||||
def log_indices(first, last, num=100):
|
def log_indices(first, last, num=100):
|
||||||
@ -26,85 +25,17 @@ def correlation(function, frames):
|
|||||||
return map(lambda f: function(start_frame, f), chain([start_frame], iterator))
|
return map(lambda f: function(start_frame, f), chain([start_frame], iterator))
|
||||||
|
|
||||||
|
|
||||||
def subensemble_correlation(selector_function, correlation_function=correlation):
|
|
||||||
def c(function, frames):
|
|
||||||
iterator = iter(frames)
|
|
||||||
start_frame = next(iterator)
|
|
||||||
selector = selector_function(start_frame)
|
|
||||||
subensemble = map(lambda f: f[selector], chain([start_frame], iterator))
|
|
||||||
return correlation_function(function, subensemble)
|
|
||||||
|
|
||||||
return c
|
|
||||||
|
|
||||||
|
|
||||||
def multi_subensemble_correlation(selector_function):
|
|
||||||
"""
|
|
||||||
selector_function has to expect a frame and to
|
|
||||||
return either valid indices (as with subensemble_correlation)
|
|
||||||
or a multidimensional array whose entries are valid indices
|
|
||||||
|
|
||||||
e.g. slice(10,100,2)
|
|
||||||
|
|
||||||
e.g. [1,2,3,4,5]
|
|
||||||
|
|
||||||
e.g. [[[0,1],[2],[3]],[[4],[5],[6]] -> shape: 2,3 with
|
|
||||||
list of indices of varying length
|
|
||||||
|
|
||||||
e.g. [slice(1653),slice(1653,None,3)]
|
|
||||||
|
|
||||||
e.g. [np.ones(len_of_frames, bool)]
|
|
||||||
|
|
||||||
in general using slices is the most efficient.
|
|
||||||
if the selections are small subsets of a frame or when many subsets are empty
|
|
||||||
using indices will be more efficient than using masks.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@set_has_counter
|
|
||||||
def cmulti(function, frames):
|
|
||||||
iterator = iter(frames)
|
|
||||||
start_frame = next(iterator)
|
|
||||||
selectors = np.asarray(selector_function(start_frame))
|
|
||||||
sel_shape = selectors.shape
|
|
||||||
if sel_shape[-1] == 0:
|
|
||||||
selectors = np.asarray(selectors, int)
|
|
||||||
if selectors.dtype != object:
|
|
||||||
sel_shape = sel_shape[:-1]
|
|
||||||
f_values = np.zeros(
|
|
||||||
sel_shape + function(start_frame, start_frame).shape,
|
|
||||||
)
|
|
||||||
count = np.zeros(sel_shape, dtype=int)
|
|
||||||
is_first_frame_loop = True
|
|
||||||
|
|
||||||
def cc(act_frame):
|
|
||||||
nonlocal is_first_frame_loop
|
|
||||||
for index in np.ndindex(sel_shape):
|
|
||||||
sel = selectors[index]
|
|
||||||
sf_sel = start_frame[sel]
|
|
||||||
if is_first_frame_loop:
|
|
||||||
count[index] = len(sf_sel)
|
|
||||||
f_values[index] = (
|
|
||||||
function(sf_sel, act_frame[sel]) if count[index] != 0 else 0
|
|
||||||
)
|
|
||||||
is_first_frame_loop = False
|
|
||||||
return np.asarray(f_values.copy())
|
|
||||||
|
|
||||||
return map(cc, chain([start_frame], iterator)), count
|
|
||||||
|
|
||||||
return cmulti
|
|
||||||
|
|
||||||
|
|
||||||
@autosave_data(2)
|
@autosave_data(2)
|
||||||
def shifted_correlation(
|
def shifted_correlation(
|
||||||
function,
|
function: Callable,
|
||||||
frames,
|
frames: Coordinates,
|
||||||
selector=None,
|
selector: ArrayLike = None,
|
||||||
multi_selector=None,
|
multi_selector: ArrayLike = None,
|
||||||
segments=10,
|
segments: int = 10,
|
||||||
skip=0.1,
|
skip: float = 0.1,
|
||||||
window=0.5,
|
window: float = 0.5,
|
||||||
average=True,
|
average: bool = True,
|
||||||
points=100,
|
points: int = 100,
|
||||||
nodes=1,
|
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Calculate the time series for a correlation function.
|
Calculate the time series for a correlation function.
|
||||||
@ -141,9 +72,6 @@ def shifted_correlation(
|
|||||||
points (int, opt.):
|
points (int, opt.):
|
||||||
The number of timeshifts for which the correlation
|
The number of timeshifts for which the correlation
|
||||||
should be calculated
|
should be calculated
|
||||||
nodes (int, opt.):
|
|
||||||
Number of nodes used for parallelization
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
tuple:
|
tuple:
|
||||||
A list of length N that contains the timeshiftes of the frames at which
|
A list of length N that contains the timeshiftes of the frames at which
|
||||||
@ -186,9 +114,7 @@ def shifted_correlation(
|
|||||||
correlation = []
|
correlation = []
|
||||||
for index in indices:
|
for index in indices:
|
||||||
correlation.append(
|
correlation.append(
|
||||||
get_correlation(
|
get_correlation(frames, start_frame, index, shifted_idx)
|
||||||
frames, start_frame, index, shifted_idx
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return correlation
|
return correlation
|
||||||
|
|
||||||
@ -202,7 +128,7 @@ def shifted_correlation(
|
|||||||
num=segments,
|
num=segments,
|
||||||
endpoint=False,
|
endpoint=False,
|
||||||
dtype=int,
|
dtype=int,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
num_frames = int(len(frames) * window)
|
num_frames = int(len(frames) * window)
|
||||||
@ -210,33 +136,18 @@ def shifted_correlation(
|
|||||||
idx = np.unique(np.int_(ls) - 1)
|
idx = np.unique(np.int_(ls) - 1)
|
||||||
t = np.array([frames[i].time for i in idx]) - frames[0].time
|
t = np.array([frames[i].time for i in idx]) - frames[0].time
|
||||||
|
|
||||||
if nodes == 1:
|
result = np.array(
|
||||||
result = np.array(
|
[
|
||||||
[
|
apply_selector(
|
||||||
apply_selector(start_frame, frames=frames, idx=idx,
|
start_frame,
|
||||||
selector=selector,
|
frames=frames,
|
||||||
multi_selector=multi_selector)
|
idx=idx,
|
||||||
for start_frame in start_frames
|
selector=selector,
|
||||||
]
|
multi_selector=multi_selector,
|
||||||
)
|
|
||||||
else:
|
|
||||||
pool = ProcessPool(nodes=nodes)
|
|
||||||
# Use try finally instead of a context manager to ensure the pool is
|
|
||||||
# restarted in case of working in a jupyter-notebook,
|
|
||||||
# otherwise the kernel has to be restarted.
|
|
||||||
try:
|
|
||||||
result = np.array(
|
|
||||||
pool.map(
|
|
||||||
partial(apply_selector,
|
|
||||||
frames=frames,
|
|
||||||
idx=idx,
|
|
||||||
selector=selector,
|
|
||||||
multi_selector=multi_selector), start_frames
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
finally:
|
for start_frame in start_frames
|
||||||
pool.terminate()
|
]
|
||||||
pool.restart()
|
)
|
||||||
|
|
||||||
if average:
|
if average:
|
||||||
clean_result = []
|
clean_result = []
|
||||||
@ -250,24 +161,62 @@ def shifted_correlation(
|
|||||||
return t, result
|
return t, result
|
||||||
|
|
||||||
|
|
||||||
def msd(start, frame):
|
def msd(
|
||||||
|
start_frame: CoordinateFrame,
|
||||||
|
end_frame: CoordinateFrame,
|
||||||
|
trajectory: Coordinates = None,
|
||||||
|
axis: str = "all",
|
||||||
|
) -> float:
|
||||||
"""
|
"""
|
||||||
Mean square displacement
|
Mean square displacement
|
||||||
"""
|
"""
|
||||||
vec = start - frame
|
if trajectory is None:
|
||||||
return (vec**2).sum(axis=1).mean()
|
displacements = start_frame - end_frame
|
||||||
|
else:
|
||||||
|
displacements = displacements_without_drift(start_frame, end_frame, trajectory)
|
||||||
|
if axis == "all":
|
||||||
|
return (displacements**2).sum(axis=1).mean()
|
||||||
|
elif axis == "x":
|
||||||
|
return (displacements[:, 0] ** 2).mean()
|
||||||
|
elif axis == "y":
|
||||||
|
return (displacements[:, 1] ** 2).mean()
|
||||||
|
elif axis == "z":
|
||||||
|
return (displacements[:, 2] ** 2).mean()
|
||||||
|
else:
|
||||||
|
raise ValueError('Parameter axis has to be ether "all", "x", "y", or "z"!')
|
||||||
|
|
||||||
|
|
||||||
def isf(start, frame, q, box=None):
|
def isf(
|
||||||
|
start_frame: CoordinateFrame,
|
||||||
|
end_frame: CoordinateFrame,
|
||||||
|
q: float = 22.7,
|
||||||
|
trajectory: Coordinates = None,
|
||||||
|
axis: str = "all",
|
||||||
|
) -> float:
|
||||||
"""
|
"""
|
||||||
Incoherent intermediate scattering function. To specify q, use
|
Incoherent intermediate scattering function. To specify q, use
|
||||||
water_isf = functools.partial(isf, q=22.77) # q has the value 22.77 nm^-1
|
water_isf = functools.partial(isf, q=22.77) # q has the value 22.77 nm^-1
|
||||||
|
|
||||||
:param q: length of scattering vector
|
:param q: length of scattering vector
|
||||||
"""
|
"""
|
||||||
vec = start - frame
|
if trajectory is None:
|
||||||
distance = (vec**2).sum(axis=1) ** 0.5
|
displacements = start_frame - end_frame
|
||||||
return np.sinc(distance * q / np.pi).mean()
|
else:
|
||||||
|
displacements = displacements_without_drift(start_frame, end_frame, trajectory)
|
||||||
|
if axis == "all":
|
||||||
|
distance = (displacements**2).sum(axis=1) ** 0.5
|
||||||
|
return np.sinc(distance * q / np.pi).mean()
|
||||||
|
elif axis == "x":
|
||||||
|
distance = np.abs(displacements[:, 0])
|
||||||
|
return np.mean(np.cos(np.abs(q * distance)))
|
||||||
|
elif axis == "y":
|
||||||
|
distance = np.abs(displacements[:, 1])
|
||||||
|
return np.mean(np.cos(np.abs(q * distance)))
|
||||||
|
elif axis == "z":
|
||||||
|
distance = np.abs(displacements[:, 2])
|
||||||
|
return np.mean(np.cos(np.abs(q * distance)))
|
||||||
|
else:
|
||||||
|
raise ValueError('Parameter axis has to be ether "all", "x", "y", or "z"!')
|
||||||
|
|
||||||
|
|
||||||
def rotational_autocorrelation(onset, frame, order=2):
|
def rotational_autocorrelation(onset, frame, order=2):
|
||||||
@ -287,16 +236,37 @@ def rotational_autocorrelation(onset, frame, order=2):
|
|||||||
return poly(scalar_prod).mean()
|
return poly(scalar_prod).mean()
|
||||||
|
|
||||||
|
|
||||||
def van_hove_self(start, end, bins):
|
def van_hove_self(
|
||||||
|
start_frame: CoordinateFrame,
|
||||||
|
end_frame: CoordinateFrame,
|
||||||
|
bins: ArrayLike,
|
||||||
|
trajectory: Coordinates = None,
|
||||||
|
axis: str = "all",
|
||||||
|
):
|
||||||
r"""
|
r"""
|
||||||
Compute the self part of the Van Hove autocorrelation function.
|
Compute the self part of the Van Hove autocorrelation function.
|
||||||
|
|
||||||
..math::
|
..math::
|
||||||
G(r, t) = \sum_i \delta(|\vec r_i(0) - \vec r_i(t)| - r)
|
G(r, t) = \sum_i \delta(|\vec r_i(0) - \vec r_i(t)| - r)
|
||||||
"""
|
"""
|
||||||
vec = start - end
|
if trajectory is None:
|
||||||
delta_r = ((vec) ** 2).sum(axis=-1) ** 0.5
|
vectors = start_frame - end_frame
|
||||||
return 1 / len(start) * histogram(delta_r, bins)[0]
|
else:
|
||||||
|
vectors = displacements_without_drift(start_frame, end_frame, trajectory)
|
||||||
|
if axis == "all":
|
||||||
|
delta_r = (vectors**2).sum(axis=1) ** 0.5
|
||||||
|
elif axis == "x":
|
||||||
|
delta_r = np.abs(vectors[:, 0])
|
||||||
|
elif axis == "y":
|
||||||
|
delta_r = np.abs(vectors[:, 1])
|
||||||
|
elif axis == "z":
|
||||||
|
delta_r = np.abs(vectors[:, 2])
|
||||||
|
else:
|
||||||
|
raise ValueError('Parameter axis has to be ether "all", "x", "y", or "z"!')
|
||||||
|
|
||||||
|
hist = np.histogram(delta_r, bins)[0]
|
||||||
|
r = (bins[1:] + bins[:-1]) / 2
|
||||||
|
return r, hist / len(start_frame)
|
||||||
|
|
||||||
|
|
||||||
def van_hove_distinct(
|
def van_hove_distinct(
|
||||||
@ -346,14 +316,18 @@ def van_hove_distinct(
|
|||||||
return hist / N
|
return hist / N
|
||||||
|
|
||||||
|
|
||||||
def overlap(onset, frame, crds_tree, radius):
|
def overlap(
|
||||||
|
start_frame: CoordinateFrame,
|
||||||
|
end_frame: CoordinateFrame,
|
||||||
|
radius: float = 0.1,
|
||||||
|
mode: str = "self",
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Compute the overlap with a reference configuration defined in a CoordinatesTree.
|
Compute the overlap with a reference configuration defined in a CoordinatesTree.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
onset: Initial frame, this is only used to get the frame index
|
onset: Initial frame, this is only used to get the frame index
|
||||||
frame: The current configuration
|
frame: The current configuration
|
||||||
crds_tree: A CoordinatesTree of the reference configurations
|
|
||||||
radius: The cutoff radius for the overlap
|
radius: The cutoff radius for the overlap
|
||||||
|
|
||||||
This function is intended to be used with :func:`shifted_correlation`.
|
This function is intended to be used with :func:`shifted_correlation`.
|
||||||
@ -369,24 +343,21 @@ def overlap(onset, frame, crds_tree, radius):
|
|||||||
... traj
|
... traj
|
||||||
... )
|
... )
|
||||||
"""
|
"""
|
||||||
tree = crds_tree[onset.step]
|
start_PBC, index_PBC = pbc_points(
|
||||||
return (tree.query(frame)[0] <= radius).sum() / tree.n
|
start_frame, start_frame.box, index=True, thickness=2 * radius
|
||||||
|
|
||||||
|
|
||||||
def susceptibility(time, correlation, **kwargs):
|
|
||||||
"""
|
|
||||||
Calculate the susceptibility of a correlation function.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
time: Timesteps of the correlation data
|
|
||||||
correlation: Value of the correlation function
|
|
||||||
**kwargs (opt.):
|
|
||||||
Additional keyword arguments will be passed to :func:`filon_fourier_transformation`.
|
|
||||||
"""
|
|
||||||
frequencies, fourier = filon_fourier_transformation(
|
|
||||||
time, correlation, imag=False, **kwargs
|
|
||||||
)
|
)
|
||||||
return frequencies, frequencies * fourier
|
start_tree = KDTree(start_PBC)
|
||||||
|
dist, index_dist = start_tree.query(end_frame, 1, distance_upper_bound=radius)
|
||||||
|
if mode == "indifferent":
|
||||||
|
return np.sum(dist <= radius) / len(start_frame)
|
||||||
|
index_dist = index_PBC[index_dist]
|
||||||
|
index = np.arange(len(start_frame))
|
||||||
|
index = index[dist <= radius]
|
||||||
|
index_dist = index_dist[dist <= radius]
|
||||||
|
if mode == "self":
|
||||||
|
return np.sum(index == index_dist) / len(start_frame)
|
||||||
|
elif mode == "distinct":
|
||||||
|
return np.sum(index != index_dist) / len(start_frame)
|
||||||
|
|
||||||
|
|
||||||
def coherent_scattering_function(onset, frame, q):
|
def coherent_scattering_function(onset, frame, q):
|
||||||
@ -414,11 +385,35 @@ def coherent_scattering_function(onset, frame, q):
|
|||||||
return coherent_sum(scfunc, onset.pbc, frame.pbc) / len(onset)
|
return coherent_sum(scfunc, onset.pbc, frame.pbc) / len(onset)
|
||||||
|
|
||||||
|
|
||||||
def non_gaussian(onset, frame):
|
def non_gaussian_parameter(
|
||||||
|
start_frame: CoordinateFrame,
|
||||||
|
end_frame: CoordinateFrame,
|
||||||
|
trajectory: Coordinates = None,
|
||||||
|
axis: str = "all",
|
||||||
|
) -> float:
|
||||||
"""
|
"""
|
||||||
Calculate the Non-Gaussian parameter :
|
Calculate the Non-Gaussian parameter :
|
||||||
..math:
|
..math:
|
||||||
\alpha_2 (t) = \frac{3}{5}\frac{\langle r_i^4(t)\rangle}{\langle r_i^2(t)\rangle^2} - 1
|
\alpha_2 (t) =
|
||||||
|
\frac{3}{5}\frac{\langle r_i^4(t)\rangle}{\langle r_i^2(t)\rangle^2} - 1
|
||||||
"""
|
"""
|
||||||
r_2 = ((frame - onset) ** 2).sum(axis=-1)
|
if trajectory is None:
|
||||||
return 3 / 5 * (r_2**2).mean() / r_2.mean() ** 2 - 1
|
vectors = start_frame - end_frame
|
||||||
|
else:
|
||||||
|
vectors = displacements_without_drift(start_frame, end_frame, trajectory)
|
||||||
|
if axis == "all":
|
||||||
|
r = (vectors**2).sum(axis=1)
|
||||||
|
dimensions = 3
|
||||||
|
elif axis == "x":
|
||||||
|
r = vectors[:, 0] ** 2
|
||||||
|
dimensions = 1
|
||||||
|
elif axis == "y":
|
||||||
|
r = vectors[:, 1] ** 2
|
||||||
|
dimensions = 1
|
||||||
|
elif axis == "z":
|
||||||
|
r = vectors[:, 2] ** 2
|
||||||
|
dimensions = 1
|
||||||
|
else:
|
||||||
|
raise ValueError('Parameter axis has to be ether "all", "x", "y", or "z"!')
|
||||||
|
|
||||||
|
return (np.mean(r**2) / ((1 + 2 / dimensions) * (np.mean(r) ** 2))) - 1
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
|
from numpy.typing import ArrayLike
|
||||||
|
from scipy import spatial
|
||||||
|
from scipy.spatial import KDTree
|
||||||
|
|
||||||
from .coordinates import rotate_axis, polar_coordinates, spherical_coordinates
|
from .coordinates import rotate_axis, polar_coordinates, Coordinates, CoordinateFrame
|
||||||
from .atoms import next_neighbors
|
from .atoms import next_neighbors
|
||||||
from .autosave import autosave_data
|
from .autosave import autosave_data
|
||||||
from .utils import runningmean
|
from .utils import runningmean
|
||||||
from .pbc import pbc_diff, pbc_points
|
from .pbc import pbc_diff, pbc_points
|
||||||
from .logging import logger
|
from .logging import logger
|
||||||
from scipy import spatial
|
|
||||||
|
|
||||||
|
|
||||||
@autosave_data(nargs=2, kwargs_keys=("coordinates_b",), version="time_average-1")
|
@autosave_data(nargs=2, kwargs_keys=("coordinates_b",), version="time_average-1")
|
||||||
@ -78,15 +80,15 @@ def time_histogram(function, coordinates, bins, hist_range, pool=None):
|
|||||||
return hist_results
|
return hist_results
|
||||||
|
|
||||||
|
|
||||||
def rdf(
|
def calc_gr(
|
||||||
atoms_a,
|
traj_a: Coordinates,
|
||||||
atoms_b=None,
|
traj_b: Coordinates = None,
|
||||||
bins=None,
|
r_max: float = None,
|
||||||
box=None,
|
delta_r: float = 0.02,
|
||||||
kind=None,
|
segments: int = 1000,
|
||||||
chunksize=50000,
|
skip: float = 0.1,
|
||||||
returnx=False,
|
remove_intra: bool = False,
|
||||||
**kwargs
|
shear: bool = False,
|
||||||
):
|
):
|
||||||
r"""
|
r"""
|
||||||
Compute the radial pair distribution of one or two sets of atoms.
|
Compute the radial pair distribution of one or two sets of atoms.
|
||||||
@ -111,161 +113,78 @@ def rdf(
|
|||||||
as large as possible, depending on the available memory.
|
as large as possible, depending on the available memory.
|
||||||
returnx (opt.): If True the x ordinate of the histogram is returned.
|
returnx (opt.): If True the x ordinate of the histogram is returned.
|
||||||
"""
|
"""
|
||||||
assert bins is not None, "Bins of the pair distribution have to be defined."
|
def gr_frame(
|
||||||
assert kind in [
|
atoms_a: CoordinateFrame,
|
||||||
"intra",
|
atoms_b: CoordinateFrame,
|
||||||
"inter",
|
bins: ArrayLike,
|
||||||
None,
|
remove_intra: bool = False,
|
||||||
], "Argument kind must be one of the following: intra, inter, None."
|
|
||||||
if box is None:
|
|
||||||
box = atoms_a.box.diagonal()
|
|
||||||
if atoms_b is None:
|
|
||||||
atoms_b = atoms_a
|
|
||||||
nr_of_atoms = len(atoms_a)
|
|
||||||
indices = np.triu_indices(nr_of_atoms, k=1)
|
|
||||||
else:
|
|
||||||
nr_a, dim = atoms_a.shape
|
|
||||||
nr_b, dim = atoms_b.shape
|
|
||||||
indices = np.array([(i, j) for i in range(nr_a) for j in range(nr_b)]).T
|
|
||||||
|
|
||||||
# compute the histogram in chunks for large systems
|
|
||||||
hist = 0
|
|
||||||
nr_of_samples = 0
|
|
||||||
for chunk in range(0, len(indices[0]), chunksize):
|
|
||||||
sl = slice(chunk, chunk + chunksize)
|
|
||||||
diff = pbc_diff(atoms_a[indices[0][sl]], atoms_b[indices[1][sl]], box)
|
|
||||||
dist = (diff**2).sum(axis=1) ** 0.5
|
|
||||||
if kind == "intra":
|
|
||||||
mask = (
|
|
||||||
atoms_a.residue_ids[indices[0][sl]]
|
|
||||||
== atoms_b.residue_ids[indices[1][sl]]
|
|
||||||
)
|
|
||||||
dist = dist[mask]
|
|
||||||
elif kind == "inter":
|
|
||||||
mask = (
|
|
||||||
atoms_a.residue_ids[indices[0][sl]]
|
|
||||||
!= atoms_b.residue_ids[indices[1][sl]]
|
|
||||||
)
|
|
||||||
dist = dist[mask]
|
|
||||||
|
|
||||||
nr_of_samples += len(dist)
|
|
||||||
hist += np.histogram(dist, bins)[0]
|
|
||||||
|
|
||||||
volume = 4 / 3 * np.pi * (bins[1:] ** 3 - bins[:-1] ** 3)
|
|
||||||
density = nr_of_samples / np.prod(box)
|
|
||||||
res = hist / volume / density
|
|
||||||
if returnx:
|
|
||||||
return np.vstack((runningmean(bins, 2), res))
|
|
||||||
else:
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
def pbc_tree_rdf(
|
|
||||||
atoms_a, atoms_b=None, bins=None, box=None, exclude=0, returnx=False, **kwargs
|
|
||||||
):
|
|
||||||
if box is None:
|
|
||||||
box = atoms_a.box.diagonal()
|
|
||||||
all_coords = pbc_points(atoms_b, box, thickness=np.amax(bins) + 0.1)
|
|
||||||
to_tree = spatial.cKDTree(all_coords)
|
|
||||||
dist = to_tree.query(
|
|
||||||
pbc_diff(atoms_a, box=box),
|
|
||||||
k=len(atoms_b),
|
|
||||||
distance_upper_bound=np.amax(bins) + 0.1,
|
|
||||||
)[0].flatten()
|
|
||||||
dist = dist[dist < np.inf]
|
|
||||||
hist = np.histogram(dist, bins)[0]
|
|
||||||
volume = 4 / 3 * np.pi * (bins[1:] ** 3 - bins[:-1] ** 3)
|
|
||||||
res = (hist) * np.prod(box) / volume / len(atoms_a) / (len(atoms_b) - exclude)
|
|
||||||
if returnx:
|
|
||||||
return np.vstack((runningmean(bins, 2), res))
|
|
||||||
else:
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
def pbc_spm_rdf(
|
|
||||||
atoms_a, atoms_b=None, bins=None, box=None, exclude=0, returnx=False, **kwargs
|
|
||||||
):
|
|
||||||
if box is None:
|
|
||||||
box = atoms_a.box
|
|
||||||
all_coords = pbc_points(atoms_b, box, thickness=np.amax(bins) + 0.1)
|
|
||||||
to_tree = spatial.cKDTree(all_coords)
|
|
||||||
if all_coords.nbytes / 1024**3 * len(atoms_a) < 2:
|
|
||||||
from_tree = spatial.cKDTree(pbc_diff(atoms_a, box=box))
|
|
||||||
dist = to_tree.sparse_distance_matrix(
|
|
||||||
from_tree, max_distance=np.amax(bins) + 0.1, output_type="ndarray"
|
|
||||||
)
|
|
||||||
dist = np.asarray(dist.tolist())[:, 2]
|
|
||||||
hist = np.histogram(dist, bins)[0]
|
|
||||||
else:
|
|
||||||
chunksize = int(
|
|
||||||
2 * len(atoms_a) / (all_coords.nbytes / 1024**3 * len(atoms_a))
|
|
||||||
)
|
|
||||||
hist = 0
|
|
||||||
for chunk in range(0, len(atoms_a), chunksize):
|
|
||||||
sl = slice(chunk, chunk + chunksize)
|
|
||||||
from_tree = spatial.cKDTree(pbc_diff(atoms_a[sl], box=box))
|
|
||||||
dist = to_tree.sparse_distance_matrix(
|
|
||||||
from_tree, max_distance=np.amax(bins) + 0.1, output_type="ndarray"
|
|
||||||
)
|
|
||||||
dist = np.asarray(dist.tolist())[:, 2]
|
|
||||||
hist += np.histogram(dist, bins)[0]
|
|
||||||
|
|
||||||
volume = 4 / 3 * np.pi * (bins[1:] ** 3 - bins[:-1] ** 3)
|
|
||||||
res = (hist) * np.prod(box) / volume / len(atoms_a) / (len(atoms_b) - exclude)
|
|
||||||
if returnx:
|
|
||||||
return np.vstack((runningmean(bins, 2), res))
|
|
||||||
else:
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
@autosave_data(nargs=2, kwargs_keys=("to_coords", "times"))
|
|
||||||
def fast_averaged_rdf(from_coords, bins, to_coords=None, times=10, exclude=0, **kwargs):
|
|
||||||
if to_coords is None:
|
|
||||||
to_coords = from_coords
|
|
||||||
exclude = 1
|
|
||||||
# first find timings for the different rdf functions
|
|
||||||
import time
|
|
||||||
|
|
||||||
# only consider sparse matrix for this condition
|
|
||||||
if (len(from_coords[0]) * len(to_coords[0]) <= 3000 * 2000) & (
|
|
||||||
len(from_coords[0]) / len(to_coords[0]) > 5
|
|
||||||
):
|
):
|
||||||
funcs = [rdf, pbc_tree_rdf, pbc_spm_rdf]
|
box = atoms_b.box
|
||||||
else:
|
n = len(atoms_a) / np.prod(np.diag(box))
|
||||||
funcs = [rdf, pbc_tree_rdf]
|
V = 4 / 3 * np.pi * bins[-1] ** 3
|
||||||
timings = []
|
particles_in_volume = int(n * V * 1.1)
|
||||||
for f in funcs:
|
if np.all(np.diag(np.diag(box)) == box):
|
||||||
start = time.time()
|
atoms_b = atoms_b % np.diag(box)
|
||||||
f(
|
atoms_b_res_ids = atoms_b.residue_ids
|
||||||
from_coords[0],
|
atoms_b_tree = KDTree(atoms_b, boxsize=np.diag(box))
|
||||||
atoms_b=to_coords[0],
|
else:
|
||||||
bins=bins,
|
atoms_b_pbc, atoms_b_pbc_index = pbc_points(
|
||||||
box=np.diag(from_coords[0].box),
|
atoms_b, box, thickness=bins[-1] + 0.1, index=True, shear=shear
|
||||||
|
)
|
||||||
|
atoms_b_res_ids = atoms_b.residue_ids[atoms_b_pbc_index]
|
||||||
|
atoms_b_tree = KDTree(atoms_b_pbc)
|
||||||
|
distances, distances_index = atoms_b_tree.query(
|
||||||
|
atoms_a, particles_in_volume, distance_upper_bound=bins[-1] + 0.1
|
||||||
)
|
)
|
||||||
end = time.time()
|
if np.array_equal(atoms_a, atoms_b):
|
||||||
timings.append(end - start)
|
distances = distances[:, 1:]
|
||||||
timings = np.array(timings)
|
distances_index = distances_index[:, 1:]
|
||||||
timings[0] = (
|
|
||||||
2 * timings[0]
|
|
||||||
) # statistics for the other functions is twice as good per frame
|
|
||||||
logger.debug("rdf function timings: " + str(timings))
|
|
||||||
rdffunc = funcs[np.argmin(timings)]
|
|
||||||
logger.debug("rdf function used: " + str(rdffunc))
|
|
||||||
if rdffunc == rdf:
|
|
||||||
times = times * 2 # duplicate times for same statistics
|
|
||||||
|
|
||||||
frames = np.array(range(0, len(from_coords), int(len(from_coords) / times)))[:times]
|
if remove_intra:
|
||||||
out = np.zeros(len(bins) - 1)
|
new_distances = []
|
||||||
for j, i in enumerate(frames):
|
for entry in list(zip(atoms_a.residue_ids, distances, distances_index)):
|
||||||
logger.debug("multi_radial_pair_distribution: %d/%d", j, len(frames))
|
mask = entry[1] < np.inf
|
||||||
out += rdffunc(
|
new_distances.append(
|
||||||
from_coords[i],
|
entry[1][mask][atoms_b_res_ids[entry[2][mask]] != entry[0]]
|
||||||
to_coords[i],
|
)
|
||||||
bins,
|
distances = np.concatenate(new_distances)
|
||||||
box=np.diag(from_coords[i].box),
|
else:
|
||||||
exclude=exclude,
|
distances = distances.flatten()
|
||||||
|
|
||||||
|
hist = np.histogram(distances, bins=bins, range=(0, bins[-1]), density=False)[0]
|
||||||
|
gr = hist / len(atoms_a)
|
||||||
|
gr = gr / (4 / 3 * np.pi * bins[1:] ** 3 - 4 / 3 * np.pi * bins[:-1] ** 3)
|
||||||
|
n = len(atoms_b) / np.prod(np.diag(atoms_b.box))
|
||||||
|
gr = gr / n
|
||||||
|
|
||||||
|
return gr, n
|
||||||
|
|
||||||
|
if traj_b is None:
|
||||||
|
traj_b = traj_a
|
||||||
|
|
||||||
|
start_frame = traj_a[int(len(traj_a) * skip)]
|
||||||
|
if r_max:
|
||||||
|
upper_bound = r_max
|
||||||
|
else:
|
||||||
|
upper_bound = round(np.min(np.diag(start_frame.box)) / 2 - 0.05, 1)
|
||||||
|
|
||||||
|
num_steps = int(upper_bound * (1 / delta_r) + 1)
|
||||||
|
bins = np.linspace(0, upper_bound, num_steps)
|
||||||
|
r = bins[1:] - (bins[1] - bins[0]) / 2
|
||||||
|
frame_indices = np.unique(
|
||||||
|
np.int_(np.linspace(len(traj_a) * skip, len(traj_a) - 1, num=segments))
|
||||||
|
)
|
||||||
|
gr = []
|
||||||
|
n = []
|
||||||
|
for frame_index in frame_indices:
|
||||||
|
result = gr_frame(
|
||||||
|
traj_a[frame_index], traj_b[frame_index], bins, remove_intra=remove_intra
|
||||||
)
|
)
|
||||||
return out / len(frames)
|
gr.append(result[0])
|
||||||
|
n.append(result[1])
|
||||||
|
gr = np.mean(gr, axis=0)
|
||||||
|
n = np.mean(n, axis=0)
|
||||||
|
return r, gr, n
|
||||||
|
|
||||||
|
|
||||||
def distance_distribution(atoms, bins):
|
def distance_distribution(atoms, bins):
|
||||||
|
@ -1,36 +1,42 @@
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
|
from scipy.special import gamma as spgamma
|
||||||
|
from scipy.integrate import quad as spquad
|
||||||
|
|
||||||
|
|
||||||
def kww(t, A, τ, β):
|
def kww(t, A, tau, beta):
|
||||||
return A * np.exp(-((t / τ) ** β))
|
return A * np.exp(-((t / tau) ** beta))
|
||||||
|
|
||||||
|
|
||||||
def kww_1e(A, τ, β):
|
def kww_1e(A, tau, beta):
|
||||||
return τ * (-np.log(1 / (np.e * A))) ** (1 / β)
|
return tau * (-np.log(1 / (np.e * A))) ** (1 / beta)
|
||||||
|
|
||||||
|
|
||||||
def cole_davidson(w, A, b, t0):
|
def cole_davidson(omega, A, beta, tau):
|
||||||
P = np.arctan(w * t0)
|
P = np.arctan(omega * tau)
|
||||||
return A * np.cos(P) ** b * np.sin(b * P)
|
return A * np.cos(P) ** beta * np.sin(beta * P)
|
||||||
|
|
||||||
|
|
||||||
def cole_cole(w, A, b, t0):
|
def cole_cole(omega, A, beta, tau):
|
||||||
return (
|
return (
|
||||||
A
|
A
|
||||||
* (w * t0) ** b
|
* (omega * tau) ** beta
|
||||||
* np.sin(np.pi * b / 2)
|
* np.sin(np.pi * beta / 2)
|
||||||
/ (1 + 2 * (w * t0) ** b * np.cos(np.pi * b / 2) + (w * t0) ** (2 * b))
|
/ (
|
||||||
|
1
|
||||||
|
+ 2 * (omega * tau) ** beta * np.cos(np.pi * beta / 2)
|
||||||
|
+ (omega * tau) ** (2 * beta)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def havriliak_negami(ω, A, β, α, τ):
|
def havriliak_negami(omega, A, beta, alpha, tau):
|
||||||
r"""
|
r"""
|
||||||
Imaginary part of the Havriliak-Negami function.
|
Imaginary part of the Havriliak-Negami function.
|
||||||
|
|
||||||
.. math::
|
.. math::
|
||||||
\chi_{HN}(\omega) = \Im\left(\frac{A}{(1 + (i\omega\tau)^\alpha)^\beta}\right)
|
\chi_{HN}(\omega) = \Im\left(\frac{A}{(1 + (i\omega\tau)^\alpha)^\beta}\right)
|
||||||
"""
|
"""
|
||||||
return -(A / (1 + (1j * ω * τ) ** α) ** β).imag
|
return -(A / (1 + (1j * omega * tau) ** alpha) ** beta).imag
|
||||||
|
|
||||||
|
|
||||||
# fits decay of correlation times, e.g. with distance to pore walls
|
# fits decay of correlation times, e.g. with distance to pore walls
|
||||||
@ -38,6 +44,47 @@ def colen(d, X, t8, A):
|
|||||||
return t8 * np.exp(A * np.exp(-d / X))
|
return t8 * np.exp(A * np.exp(-d / X))
|
||||||
|
|
||||||
|
|
||||||
# fits decay of the plateau height of the overlap function, e.g. with distance to pore walls
|
# fits decay of the plateau height of the overlap function,
|
||||||
|
# e.g. with distance to pore walls
|
||||||
def colenQ(d, X, Qb, g):
|
def colenQ(d, X, Qb, g):
|
||||||
return (1 - Qb) * np.exp(-((d / X) ** g)) + Qb
|
return (1 - Qb) * np.exp(-((d / X) ** g)) + Qb
|
||||||
|
|
||||||
|
|
||||||
|
def vft(T, tau_0, B, T_inf):
|
||||||
|
return tau_0 * np.exp(B / (T - T_inf))
|
||||||
|
|
||||||
|
|
||||||
|
def arrhenius(T, tau_0, E_a):
|
||||||
|
return tau_0 * np.exp(E_a / T)
|
||||||
|
|
||||||
|
|
||||||
|
def MLfit(t, tau, A, alpha):
|
||||||
|
def MLf(z, a):
|
||||||
|
"""Mittag-Leffler function"""
|
||||||
|
z = np.atleast_1d(z)
|
||||||
|
if a == 0:
|
||||||
|
return 1 / (1 - z)
|
||||||
|
elif a == 1:
|
||||||
|
return np.exp(z)
|
||||||
|
elif a > 1 or all(z > 0):
|
||||||
|
k = np.arange(100)
|
||||||
|
return np.polynomial.polynomial.polyval(z, 1 / spgamma(a * k + 1))
|
||||||
|
|
||||||
|
# a helper for tricky case, from Gorenflo, Loutchko & Luchko
|
||||||
|
def _MLf(z, a):
|
||||||
|
if z < 0:
|
||||||
|
f = lambda x: (
|
||||||
|
np.exp(-x * (-z) ** (1 / a))
|
||||||
|
* x ** (a - 1)
|
||||||
|
* np.sin(np.pi * a)
|
||||||
|
/ (x ** (2 * a) + 2 * x**a * np.cos(np.pi * a) + 1)
|
||||||
|
)
|
||||||
|
return 1 / np.pi * spquad(f, 0, np.inf)[0]
|
||||||
|
elif z == 0:
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
return MLf(z, a)
|
||||||
|
|
||||||
|
return np.vectorize(_MLf)(z, a)
|
||||||
|
|
||||||
|
return A * MLf(-((t / tau) ** alpha), alpha)
|
||||||
|
44
src/mdevaluate/system.py
Normal file
44
src/mdevaluate/system.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import abc
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from subprocess import run
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(kw_only=True)
|
||||||
|
class MDSystem(abc.ABC):
|
||||||
|
load_only_results: bool = False
|
||||||
|
system_dir: str = field(init=False)
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def _add_description(self, data: pd.DataFrame) -> pd.DataFrame:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def save_results(self, data: pd.DataFrame, key: str) -> None:
|
||||||
|
data = self._add_description(data)
|
||||||
|
hdf5_file = f"{self.system_dir}/out/results.h5"
|
||||||
|
data.to_hdf(hdf5_file, key=key, complevel=9, complib="blosc")
|
||||||
|
|
||||||
|
def load_results(self, key: str) -> pd.DataFrame:
|
||||||
|
hdf5_file = f"{self.system_dir}/out/results.h5"
|
||||||
|
data = pd.read_hdf(hdf5_file, key=key)
|
||||||
|
if isinstance(data, pd.DataFrame):
|
||||||
|
return data
|
||||||
|
else:
|
||||||
|
raise TypeError("Result is not a DataFrame!")
|
||||||
|
|
||||||
|
def cleanup_results(self) -> None:
|
||||||
|
hdf5_file = f"{self.system_dir}/out/results.h5"
|
||||||
|
hdf5_temp_file = f"{self.system_dir}/out/results_temp.h5"
|
||||||
|
run(
|
||||||
|
[
|
||||||
|
"ptrepack",
|
||||||
|
"--chunkshape=auto",
|
||||||
|
"--propindexes",
|
||||||
|
"--complevel=9",
|
||||||
|
"--complib=blosc",
|
||||||
|
hdf5_file,
|
||||||
|
hdf5_temp_file,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
run(["mv", hdf5_temp_file, hdf5_file])
|
@ -25,7 +25,8 @@ def five_point_stencil(xdata, ydata):
|
|||||||
ydata: y values of the data points
|
ydata: y values of the data points
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Values where the derivative was estimated and the value of the derivative at these points.
|
Values where the derivative was estimated and the value of the derivative at
|
||||||
|
these points.
|
||||||
|
|
||||||
This algorithm is only valid for values on a regular grid, for unevenly distributed
|
This algorithm is only valid for values on a regular grid, for unevenly distributed
|
||||||
data it is only an approximation, albeit a quite good one.
|
data it is only an approximation, albeit a quite good one.
|
||||||
@ -33,17 +34,17 @@ def five_point_stencil(xdata, ydata):
|
|||||||
See: https://en.wikipedia.org/wiki/Five-point_stencil
|
See: https://en.wikipedia.org/wiki/Five-point_stencil
|
||||||
"""
|
"""
|
||||||
return xdata[2:-2], (
|
return xdata[2:-2], (
|
||||||
(-ydata[4:] + 8 * ydata[3:-1] - 8 * ydata[1:-3] + ydata[:-4])
|
(-ydata[4:] + 8 * ydata[3:-1] - 8 * ydata[1:-3] + ydata[:-4])
|
||||||
/ (3 * (xdata[4:] - xdata[:-4]))
|
/ (3 * (xdata[4:] - xdata[:-4]))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def filon_fourier_transformation(
|
def filon_fourier_transformation(
|
||||||
time,
|
time,
|
||||||
correlation,
|
correlation,
|
||||||
frequencies=None,
|
frequencies=None,
|
||||||
derivative="linear",
|
derivative="linear",
|
||||||
imag=True,
|
imag=True,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Fourier-transformation for slow varrying functions. The filon algorithmus is
|
Fourier-transformation for slow varrying functions. The filon algorithmus is
|
||||||
@ -63,8 +64,8 @@ def filon_fourier_transformation(
|
|||||||
If frequencies are not explicitly given they will be evenly placed on a log scale
|
If frequencies are not explicitly given they will be evenly placed on a log scale
|
||||||
in the interval [1/tmax, 0.1/tmin] where tmin and tmax are the smallest respectively
|
in the interval [1/tmax, 0.1/tmin] where tmin and tmax are the smallest respectively
|
||||||
the biggest time (greater than 0) of the provided times. The frequencies are cut off
|
the biggest time (greater than 0) of the provided times. The frequencies are cut off
|
||||||
at high values by one decade, since the fourier transformation deviates quite strongly
|
at high values by one decade, since the fourier transformation deviates quite
|
||||||
in this regime.
|
strongly in this regime.
|
||||||
|
|
||||||
.. [Blochowicz]
|
.. [Blochowicz]
|
||||||
T. Blochowicz, Broadband dielectric spectroscopy in neat and binary
|
T. Blochowicz, Broadband dielectric spectroscopy in neat and binary
|
||||||
@ -86,25 +87,26 @@ def filon_fourier_transformation(
|
|||||||
derivative.reshape(-1, 1)
|
derivative.reshape(-1, 1)
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
'Invalid approximation method {}. Possible values are "linear", "stencil" or a list of values.'
|
'Invalid approximation method {}. Possible values are "linear", "stencil" '
|
||||||
|
'or a list of values.'
|
||||||
)
|
)
|
||||||
time = time.reshape(-1, 1)
|
time = time.reshape(-1, 1)
|
||||||
|
|
||||||
integral = (
|
integral = (
|
||||||
np.cos(frequencies * time[1:]) - np.cos(frequencies * time[:-1])
|
np.cos(frequencies * time[1:]) - np.cos(frequencies * time[:-1])
|
||||||
) / frequencies**2
|
) / frequencies ** 2
|
||||||
fourier = (derivative * integral).sum(axis=0)
|
fourier = (derivative * integral).sum(axis=0)
|
||||||
|
|
||||||
if imag:
|
if imag:
|
||||||
integral = (
|
integral = (
|
||||||
1j
|
1j
|
||||||
* (np.sin(frequencies * time[1:]) - np.sin(frequencies * time[:-1]))
|
* (np.sin(frequencies * time[1:]) - np.sin(frequencies * time[:-1]))
|
||||||
/ frequencies**2
|
/ frequencies ** 2
|
||||||
)
|
)
|
||||||
fourier = (
|
fourier = (
|
||||||
fourier
|
fourier
|
||||||
+ (derivative * integral).sum(axis=0)
|
+ (derivative * integral).sum(axis=0)
|
||||||
+ 1j * correlation[0] / frequencies
|
+ 1j * correlation[0] / frequencies
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -219,12 +221,11 @@ def coherent_sum(func, coord_a, coord_b):
|
|||||||
coherent_sum(f, x, y) == f(x.reshape(N, 1, d), x.reshape(1, M, d)).sum()
|
coherent_sum(f, x, y) == f(x.reshape(N, 1, d), x.reshape(1, M, d)).sum()
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
func: The function is called for each two items in both arrays, this should return a scalar value.
|
func: The function is called for each two items in both arrays, this should
|
||||||
|
return a scalar value.
|
||||||
coord_a, coord_b: The two arrays.
|
coord_a, coord_b: The two arrays.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if isinstance(func, FunctionType):
|
|
||||||
func = numba.jit(func, nopython=True, cache=True)
|
|
||||||
|
|
||||||
def cohsum(coord_a, coord_b):
|
def cohsum(coord_a, coord_b):
|
||||||
res = 0
|
res = 0
|
||||||
@ -244,17 +245,18 @@ def coherent_histogram(func, coord_a, coord_b, bins, distinct=False):
|
|||||||
N, d = x.shape
|
N, d = x.shape
|
||||||
M, d = y.shape
|
M, d = y.shape
|
||||||
bins = np.arange(1, 5, 0.1)
|
bins = np.arange(1, 5, 0.1)
|
||||||
coherent_histogram(f, x, y, bins) == histogram(f(x.reshape(N, 1, d), x.reshape(1, M, d)), bins=bins)
|
coherent_histogram(f, x, y, bins) == histogram(
|
||||||
|
f(x.reshape(N, 1, d), x.reshape(1, M, d)), bins=bins
|
||||||
|
)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
func: The function is called for each two items in both arrays, this should return a scalar value.
|
func: The function is called for each two items in both arrays, this should
|
||||||
|
return a scalar value.
|
||||||
coord_a, coord_b: The two arrays.
|
coord_a, coord_b: The two arrays.
|
||||||
bins: The bins used for the histogram must be distributed regular on a linear scale.
|
bins: The bins used for the histogram must be distributed regular on a linear
|
||||||
|
scale.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if isinstance(func, FunctionType):
|
|
||||||
func = numba.jit(func, nopython=True, cache=True)
|
|
||||||
|
|
||||||
assert np.isclose(
|
assert np.isclose(
|
||||||
np.diff(bins).mean(), np.diff(bins)
|
np.diff(bins).mean(), np.diff(bins)
|
||||||
).all(), "A regular distribution of bins is required."
|
).all(), "A regular distribution of bins is required."
|
||||||
@ -278,7 +280,8 @@ def coherent_histogram(func, coord_a, coord_b, bins, distinct=False):
|
|||||||
|
|
||||||
def Sq_from_gr(r, gr, q, ρ):
|
def Sq_from_gr(r, gr, q, ρ):
|
||||||
r"""
|
r"""
|
||||||
Compute the static structure factor as fourier transform of the pair correlation function. [Yarnell]_
|
Compute the static structure factor as fourier transform of the pair correlation
|
||||||
|
function. [Yarnell]_
|
||||||
|
|
||||||
.. math::
|
.. math::
|
||||||
S(q) - 1 = \\frac{4\\pi \\rho}{q}\\int\\limits_0^\\infty (g(r) - 1)\\,r \\sin(qr) dr
|
S(q) - 1 = \\frac{4\\pi \\rho}{q}\\int\\limits_0^\\infty (g(r) - 1)\\,r \\sin(qr) dr
|
||||||
@ -290,7 +293,8 @@ def Sq_from_gr(r, gr, q, ρ):
|
|||||||
ρ: Average number density
|
ρ: Average number density
|
||||||
|
|
||||||
.. [Yarnell]
|
.. [Yarnell]
|
||||||
Yarnell, J. L., Katz, M. J., Wenzel, R. G., & Koenig, S. H. (1973). Physical Review A, 7(6), 2130–2144.
|
Yarnell, J. L., Katz, M. J., Wenzel, R. G., & Koenig, S. H. (1973). Physical
|
||||||
|
Review A, 7(6), 2130–2144.
|
||||||
http://doi.org/10.1017/CBO9781107415324.004
|
http://doi.org/10.1017/CBO9781107415324.004
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@ -300,7 +304,8 @@ def Sq_from_gr(r, gr, q, ρ):
|
|||||||
|
|
||||||
def Fqt_from_Grt(data, q):
|
def Fqt_from_Grt(data, q):
|
||||||
"""
|
"""
|
||||||
Calculate the ISF from the van Hove function for a given q value by fourier transform.
|
Calculate the ISF from the van Hove function for a given q value by fourier
|
||||||
|
transform.
|
||||||
|
|
||||||
.. math::
|
.. math::
|
||||||
F_q(t) = \\int\\limits_0^\\infty dr \\; G(r, t) \\frac{\\sin(qr)}{qr}
|
F_q(t) = \\int\\limits_0^\\infty dr \\; G(r, t) \\frac{\\sin(qr)}{qr}
|
||||||
@ -312,8 +317,9 @@ def Fqt_from_Grt(data, q):
|
|||||||
q: Value of q.
|
q: Value of q.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
If input data was a dataframe the result will be returned as one too, else two arrays
|
If input data was a dataframe the result will be returned as one too, else two
|
||||||
will be returned, which will contain times and values of Fq(t) respectively.
|
arrays will be returned, which will contain times and values of Fq(t)
|
||||||
|
respectively.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if isinstance(data, pd.DataFrame):
|
if isinstance(data, pd.DataFrame):
|
||||||
@ -329,7 +335,10 @@ def Fqt_from_Grt(data, q):
|
|||||||
|
|
||||||
|
|
||||||
def singledispatchmethod(func):
|
def singledispatchmethod(func):
|
||||||
"""A decorator to define a genric instance method, analogue to functools.singledispatch."""
|
"""
|
||||||
|
A decorator to define a genric instance method, analogue to
|
||||||
|
functools.singledispatch.
|
||||||
|
"""
|
||||||
dispatcher = functools.singledispatch(func)
|
dispatcher = functools.singledispatch(func)
|
||||||
|
|
||||||
def wrapper(*args, **kw):
|
def wrapper(*args, **kw):
|
||||||
@ -341,7 +350,9 @@ def singledispatchmethod(func):
|
|||||||
|
|
||||||
|
|
||||||
def histogram(data, bins):
|
def histogram(data, bins):
|
||||||
"""Compute the histogram of the given data. Uses numpy.bincount function, if possible."""
|
"""
|
||||||
|
Compute the histogram of the given data. Uses numpy.bincount function, if possible.
|
||||||
|
"""
|
||||||
dbins = np.diff(bins)
|
dbins = np.diff(bins)
|
||||||
dx = dbins.mean()
|
dx = dbins.mean()
|
||||||
if bins.min() == 0 and dbins.std() < 1e-6:
|
if bins.min() == 0 and dbins.std() < 1e-6:
|
||||||
@ -358,7 +369,8 @@ def quick1etau(t, C, n=7):
|
|||||||
Estimate the time for a correlation function that goes from 1 to 0 to decay to 1/e.
|
Estimate the time for a correlation function that goes from 1 to 0 to decay to 1/e.
|
||||||
|
|
||||||
If successful, returns tau as fine interpolation with a kww fit.
|
If successful, returns tau as fine interpolation with a kww fit.
|
||||||
The data is reduce to points around 1/e to remove short and long times from the kww fit!
|
The data is reduce to points around 1/e to remove short and long times from the kww
|
||||||
|
fit!
|
||||||
t is the time
|
t is the time
|
||||||
C is C(t) the correlation function
|
C is C(t) the correlation function
|
||||||
n is the minimum number of points around 1/e required
|
n is the minimum number of points around 1/e required
|
||||||
@ -384,3 +396,93 @@ def quick1etau(t, C, n=7):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
return tau_est
|
return tau_est
|
||||||
|
|
||||||
|
|
||||||
|
def susceptibility(time, correlation, **kwargs):
|
||||||
|
"""
|
||||||
|
Calculate the susceptibility of a correlation function.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
time: Timesteps of the correlation data
|
||||||
|
correlation: Value of the correlation function
|
||||||
|
**kwargs (opt.):
|
||||||
|
Additional keyword arguments will be passed to :func:`filon_fourier_transformation`.
|
||||||
|
"""
|
||||||
|
frequencies, fourier = filon_fourier_transformation(
|
||||||
|
time, correlation, imag=False, **kwargs
|
||||||
|
)
|
||||||
|
return frequencies, frequencies * fourier
|
||||||
|
|
||||||
|
|
||||||
|
def read_gro(file):
|
||||||
|
with open(file, "r") as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
description = lines[0].splitlines()[0]
|
||||||
|
boxsize = lines[-1]
|
||||||
|
box = boxsize.split()
|
||||||
|
|
||||||
|
if len(box) == 3:
|
||||||
|
box = np.array([[box[0], 0, 0], [0, box[1], 0], [0, 0, box[2]]], dtype=float)
|
||||||
|
else:
|
||||||
|
box = np.array(
|
||||||
|
[
|
||||||
|
[box[0], box[3], box[4]],
|
||||||
|
[box[5], box[1], box[6]],
|
||||||
|
[box[7], box[8], box[2]],
|
||||||
|
],
|
||||||
|
dtype=float,
|
||||||
|
)
|
||||||
|
|
||||||
|
atomdata = np.genfromtxt(
|
||||||
|
file,
|
||||||
|
delimiter=[5, 5, 5, 5, 8, 8, 8],
|
||||||
|
dtype="i8,U5,U5,i8,f8,f8,f8",
|
||||||
|
skip_header=2,
|
||||||
|
skip_footer=1,
|
||||||
|
unpack=True,
|
||||||
|
)
|
||||||
|
atoms_DF = pd.DataFrame(
|
||||||
|
{
|
||||||
|
"residue_id": atomdata[0],
|
||||||
|
"residue_name": atomdata[1],
|
||||||
|
"atom_name": atomdata[2],
|
||||||
|
"atom_id": atomdata[3],
|
||||||
|
"pos_x": atomdata[4],
|
||||||
|
"pos_y": atomdata[5],
|
||||||
|
"pos_z": atomdata[6],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return atoms_DF, box, description
|
||||||
|
|
||||||
|
|
||||||
|
def write_gro(file, atoms_DF, box, description):
|
||||||
|
with open(file, "w") as f:
|
||||||
|
f.write(f"{description} \n")
|
||||||
|
f.write(f"{len(atoms_DF)}\n")
|
||||||
|
for i, atom in atoms_DF.iterrows():
|
||||||
|
f.write(
|
||||||
|
f"{atom['residue_id']:>5}{atom['residue_name']:<5}"
|
||||||
|
f"{atom['atom_name']:>5}{atom['atom_id']:>5}"
|
||||||
|
f"{atom['pos_x']:8.3f}{atom['pos_y']:8.3f}"
|
||||||
|
f"{atom['pos_z']:8.3f}\n"
|
||||||
|
)
|
||||||
|
f.write(
|
||||||
|
f"{box[0,0]:10.5f}{box[1,1]:10.5f}{box[2,2]:10.5f}"
|
||||||
|
f"{box[0,1]:10.5f}{box[0,2]:10.5f}{box[1,0]:10.5f}"
|
||||||
|
f"{box[1,2]:10.5f}{box[2,0]:10.5f}{box[2,1]:10.5f}\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def fibonacci_sphere(samples=1000):
|
||||||
|
points = []
|
||||||
|
phi = np.pi * (np.sqrt(5.0) - 1.0) # golden angle in radians
|
||||||
|
|
||||||
|
for i in range(samples):
|
||||||
|
y = 1 - (i / float(samples - 1)) * 2 # y goes from 1 to -1
|
||||||
|
radius = np.sqrt(1 - y * y) # radius at y
|
||||||
|
theta = phi * i # golden angle increment
|
||||||
|
x = np.cos(theta) * radius
|
||||||
|
z = np.sin(theta) * radius
|
||||||
|
points.append((x, y, z))
|
||||||
|
|
||||||
|
return np.array(points)
|
||||||
|
Loading…
Reference in New Issue
Block a user