dynasor 2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. dynasor/__init__.py +28 -0
  2. dynasor/cli/__init__.py +0 -0
  3. dynasor/cli/main.py +204 -0
  4. dynasor/core/__init__.py +0 -0
  5. dynasor/core/reciprocal.py +50 -0
  6. dynasor/core/rho_j_q_numba.py +127 -0
  7. dynasor/core/time_averager.py +44 -0
  8. dynasor/correlation_functions.py +538 -0
  9. dynasor/logging_tools.py +46 -0
  10. dynasor/post_processing/__init__.py +21 -0
  11. dynasor/post_processing/atomic_weighting.py +147 -0
  12. dynasor/post_processing/average_runs.py +61 -0
  13. dynasor/post_processing/electron_scattering_factors.py +170 -0
  14. dynasor/post_processing/filon.py +148 -0
  15. dynasor/post_processing/form-factors/compton-parameters-waasmaier-kirfel-1995.json +1 -0
  16. dynasor/post_processing/form-factors/electron-parameters-ions-peng-1998.json +1 -0
  17. dynasor/post_processing/form-factors/electron-parameters-kmax2-peng-1996.json +1 -0
  18. dynasor/post_processing/form-factors/electron-parameters-kmax6-peng-1996.json +1 -0
  19. dynasor/post_processing/form-factors/neutron_scattering_lengths.json +1 -0
  20. dynasor/post_processing/form-factors/x-ray-parameters-itcc-2006.json +1 -0
  21. dynasor/post_processing/form-factors/x-ray-parameters-waasmaier-kirfel-1995.json +1 -0
  22. dynasor/post_processing/neutron_scattering_lengths.py +137 -0
  23. dynasor/post_processing/spherical_average.py +225 -0
  24. dynasor/post_processing/weights.py +72 -0
  25. dynasor/post_processing/x_ray_form_factors.py +186 -0
  26. dynasor/qpoints/__init__.py +6 -0
  27. dynasor/qpoints/lattice.py +136 -0
  28. dynasor/qpoints/spherical_qpoints.py +171 -0
  29. dynasor/qpoints/tools.py +247 -0
  30. dynasor/sample.py +204 -0
  31. dynasor/tools/__init__.py +0 -0
  32. dynasor/tools/acfs.py +117 -0
  33. dynasor/tools/damped_harmonic_oscillator.py +114 -0
  34. dynasor/tools/structures.py +102 -0
  35. dynasor/trajectory/__init__.py +4 -0
  36. dynasor/trajectory/abstract_trajectory_reader.py +38 -0
  37. dynasor/trajectory/ase_trajectory_reader.py +63 -0
  38. dynasor/trajectory/atomic_indices.py +37 -0
  39. dynasor/trajectory/extxyz_trajectory_reader.py +138 -0
  40. dynasor/trajectory/lammps_trajectory_reader.py +209 -0
  41. dynasor/trajectory/mdanalysis_trajectory_reader.py +139 -0
  42. dynasor/trajectory/trajectory.py +300 -0
  43. dynasor/trajectory/trajectory_frame.py +157 -0
  44. dynasor/units.py +42 -0
  45. dynasor-2.2.dist-info/METADATA +64 -0
  46. dynasor-2.2.dist-info/RECORD +50 -0
  47. dynasor-2.2.dist-info/WHEEL +5 -0
  48. dynasor-2.2.dist-info/entry_points.txt +2 -0
  49. dynasor-2.2.dist-info/licenses/LICENSE +21 -0
  50. dynasor-2.2.dist-info/top_level.txt +1 -0
dynasor/__init__.py ADDED
@@ -0,0 +1,28 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ """
4
+ dynasor module.
5
+ """
6
+
7
+ from .correlation_functions import (
8
+ compute_dynamic_structure_factors,
9
+ compute_spectral_energy_density,
10
+ compute_static_structure_factors
11
+ )
12
+ from .qpoints import (
13
+ get_spherical_qpoints,
14
+ get_supercell_qpoints_along_path
15
+ )
16
+ from .sample import read_sample_from_npz
17
+ from .trajectory import Trajectory
18
+
19
+ __version__ = '2.2'
20
+ __all__ = [
21
+ 'compute_dynamic_structure_factors',
22
+ 'compute_spectral_energy_density',
23
+ 'compute_static_structure_factors',
24
+ 'get_spherical_qpoints',
25
+ 'get_supercell_qpoints_along_path',
26
+ 'read_sample_from_npz',
27
+ 'Trajectory',
28
+ ]
File without changes
dynasor/cli/main.py ADDED
@@ -0,0 +1,204 @@
1
+ #!/usr/bin/python3
2
+
3
+ import sys
4
+ import argparse
5
+ import numpy as np
6
+
7
+ from dynasor.logging_tools import logger, set_logging_level
8
+ from dynasor.qpoints import get_spherical_qpoints
9
+ from dynasor.trajectory import Trajectory
10
+ from dynasor.correlation_functions import compute_dynamic_structure_factors
11
+ from dynasor.post_processing import get_spherically_averaged_sample_binned
12
+
13
+
14
+ def main():
15
+
16
+ parser = argparse.ArgumentParser(
17
+ description='dynasor is a simple tool for calculating total and partial dynamic structure'
18
+ ' factors as well as current correlation functions from molecular dynamics simulations.'
19
+ ' The main input consists of a trajectory output from a MD simulation, i.e., a file'
20
+ ' containing snapshots of the particle coordinates and optionally velocities that'
21
+ ' correspond to consecutive, equally spaced points in (simulation) time.'
22
+ '\n'
23
+ ' Dynasor has recently been updated. Some of the old options have new names and'
24
+ ' some new options have been added. If your script has stopped working, check the'
25
+ ' options with "dynasor --help". In addition, we would like to let you know that'
26
+ ' dynasor now has a Python interface, which gives you acces to more'
27
+ ' functionality and options.')
28
+
29
+ iogroup = parser.add_argument_group(
30
+ 'Input/output options',
31
+ 'Options controlling input and output, files and fileformats.')
32
+ iogroup.add_argument(
33
+ '-f', '--trajectory', type=str, metavar='TRAJECTORY_FILE',
34
+ help='Molecular dynamics trajectory file to be analyzed.'
35
+ ' Supported formats depends on MDAnalysis. As a fallback, a lammps-trajectory parser'
36
+ ' implemented in Python is also available as well as an extended-xyz reader based on ASE')
37
+ iogroup.add_argument(
38
+ '--trajectory-format', type=str, metavar='TRAJECTORY_FORMAT',
39
+ help='Format of trajectory. Choose from: "lammps_internal", "extxyz", or one'
40
+ ' of the formats supported by MDAnalysis (except "lammpsdump", which is'
41
+ ' called through "lammps_mdanalysis" to avoid ambiguity)')
42
+ iogroup.add_argument(
43
+ '--length-unit', type=str, metavar='LENGTH_UNIT', default='Angstrom',
44
+ help='Length unit of trajectory ("Angstrom", "nm", "pm", "fm"). Necessary for correct '
45
+ 'conversion to internal dynasor units if the trajectory file does not contain '
46
+ 'unit information.')
47
+ iogroup.add_argument(
48
+ '--time-unit', type=str, metavar='TIME_UNIT', default='fs',
49
+ help='Time unit of trajectory ("fs", "ps", "ns"). Necessary for correct conversion to '
50
+ 'internal dynasor units if the trajectory file does not contain unit information.')
51
+ iogroup.add_argument(
52
+ '-n', '--index', type=str, metavar='INDEX_FILE',
53
+ help='Optional index file (think Gromacs NDX-style) for specifying atom types. Atoms are'
54
+ ' indexed from 1 up to N (total number of atoms). It is possible to index only a subset of'
55
+ ' all atoms, and atoms can be indexed in more than one group. If no index file is provided,'
56
+ ' all atoms will be considered identical.')
57
+ iogroup.add_argument(
58
+ '--outfile', type=str, metavar='FILE',
59
+ help='Write output to FILE as a numpy npz file')
60
+
61
+ qspace = parser.add_argument_group(
62
+ 'General q-space options',
63
+ 'Options controlling general aspects for how q-space should be sampled and collected.')
64
+ qspace.add_argument(
65
+ '--q-sampling', type=str,
66
+ metavar='STYLE', default='isotropic',
67
+ help='Possible values are "isotropic" (default) for sampling isotropic systems'
68
+ ' (as liquids), and "line" to sample uniformly along a certain direction in q-space. ')
69
+ defval = 80
70
+ qspace.add_argument(
71
+ '--q-bins',
72
+ metavar='BINS', type=int, default=defval,
73
+ help='Number of "radial" bins to use (between 0 and largest |q|-value) when collecting'
74
+ f' resulting average. Default value is {defval}.')
75
+
76
+ qiso = parser.add_argument_group('Isotropic q-space sampling')
77
+ defval = 20000
78
+ qiso.add_argument(
79
+ '--max-q-points', metavar='QPOINTS', type=int,
80
+ default=defval,
81
+ help='Maximum number of points used to sample q-space. Puts an (approximate) upper'
82
+ f' limit by randomly selecting. points. Default value is {defval}.')
83
+ defval = 60
84
+ qiso.add_argument(
85
+ '--q-max', metavar='QMAX', type=int, default=defval,
86
+ help='Largest q-value to consider in units of "2*pi*Å^-1".'
87
+ ' Default value for QMAX is {defval}.')
88
+
89
+ qline = parser.add_argument_group('Line-style q-space sampling')
90
+ qline.add_argument(
91
+ '--q-direction', metavar='QDIRECTION',
92
+ help='Direction along which to sample. QPOINTS points will be evenly placed between'
93
+ ' 0,0,0 and QDIRECTION. Given as three comma separated values.')
94
+ defval = 100
95
+ qline.add_argument(
96
+ '--q-points', metavar='QPOINTS', type=int, default=defval,
97
+ help=f'Number of q-points to sample along line. Default: {defval}')
98
+
99
+ tgroup = parser.add_argument_group(
100
+ 'Time-related options',
101
+ 'Options controlling timestep, length and shape of trajectory frame window, etc.')
102
+ tgroup.add_argument(
103
+ '--time-window', metavar='TIME_WINDOW', type=int,
104
+ help='The length of the trajectory frame window to use for time correlation calculation.'
105
+ ' It is expressed in number of frames and determines, among other things, the smallest'
106
+ ' frequency that can be resolved. If no TIME_WINDOW is provided, only static (t=0)'
107
+ ' correlations will be calculated')
108
+ defval = 100
109
+ tgroup.add_argument(
110
+ '--max-frames', metavar='FRAMES', type=int, default=defval,
111
+ help='Limits the total number of trajectory frames read to FRAMES.'
112
+ f' The default value is {defval}.')
113
+ defval = 1
114
+ tgroup.add_argument(
115
+ '--step', metavar='STEP', type=int, default=defval,
116
+ help='Only use every STEP-th trajectory frame. The default STEP is {defval}, meaning'
117
+ ' every frame is processed. STEP affects dt and hence the smallest time resolved.')
118
+ defval = 1
119
+ tgroup.add_argument(
120
+ '--stride', metavar='STRIDE', type=int, default=defval,
121
+ help='STRIDE number of frames between consecutive trajectory windows. This does not affect'
122
+ ' dt. If e.g. STRIDE > TIME_CORR_STEPS, some frames will be completely unused.')
123
+ tgroup.add_argument(
124
+ '--dt', metavar='DELTATIME', type=float,
125
+ help='Explicitly sets the time difference between two consecutively processed'
126
+ ' trajectory frames to DELTATIME (femtoseconds). ')
127
+
128
+ options = parser.add_argument_group('General processing options')
129
+ options.add_argument(
130
+ '--calculate-incoherent',
131
+ action='store_true', default=False,
132
+ help='Calculate the incoherent part. Default is False.')
133
+ options.add_argument(
134
+ '--calculate-currents',
135
+ action='store_true', default=False,
136
+ help='Calculate the current (velocity) correlations, Default is False.')
137
+
138
+ parser.add_argument(
139
+ '-q', '--quiet', action='count', default=0,
140
+ help='Increase quietness (opposite of verbosity).')
141
+ parser.add_argument(
142
+ '-v', '--verbose', action='count', default=0,
143
+ help='Increase verbosity (opposite of quietness).')
144
+
145
+ args = parser.parse_args()
146
+
147
+ # set log level
148
+ quietness = args.quiet - args.verbose
149
+ if quietness < 0:
150
+ log_level = 'DEBUG'
151
+ elif quietness == 0:
152
+ log_level = 'INFO'
153
+ elif quietness == 1:
154
+ log_level = 'WARN'
155
+ elif quietness == 2:
156
+ log_level = 'ERROR'
157
+ else:
158
+ log_level = 'CRITICAL'
159
+ set_logging_level(log_level)
160
+
161
+ # parse args
162
+ if args.trajectory is None:
163
+ logger.error('A trajectory must be specified. Use option -f')
164
+ sys.exit(1)
165
+
166
+ if not args.outfile:
167
+ logger.error('An output file must be specified. Use option --outfile')
168
+ sys.exit(1)
169
+
170
+ if args.dt is None:
171
+ logger.info('No value set for dt. Setting to 1 fs. Note that this is irrelevant when only '
172
+ 'computing static structure factors, i.e., if time_window is not set.')
173
+ args.dt = 1
174
+
175
+ # setup Trajectory
176
+ traj = Trajectory(args.trajectory,
177
+ trajectory_format=args.trajectory_format,
178
+ atomic_indices=args.index,
179
+ length_unit=args.length_unit,
180
+ time_unit=args.time_unit,
181
+ frame_stop=args.max_frames,
182
+ frame_step=args.step,
183
+ )
184
+
185
+ # setup q-points
186
+ if args.q_sampling == 'line':
187
+ q_dir = np.array(args.q_direction)
188
+ n_qpoints = args.q_points
189
+ q_points = np.array([i * q_dir for i in np.linspace(0, 1, n_qpoints)])
190
+ elif args.q_sampling == 'isotropic':
191
+ q_points = get_spherical_qpoints(traj.cell, args.q_max, args.max_q_points)
192
+
193
+ # run dynasor calculation
194
+ sample = compute_dynamic_structure_factors(
195
+ traj, q_points=q_points,
196
+ dt=args.dt, window_size=args.time_window, window_step=args.stride,
197
+ calculate_currents=args.calculate_currents,
198
+ calculate_incoherent=args.calculate_incoherent,
199
+ )
200
+
201
+ # save results to file
202
+ if args.q_sampling == 'isotropic':
203
+ sample = get_spherically_averaged_sample_binned(sample, num_q_bins=args.q_bins)
204
+ sample.write_to_npz(args.outfile)
File without changes
@@ -0,0 +1,50 @@
1
+ import numpy as np
2
+
3
+ from dynasor.core.rho_j_q_numba import rho_q as rho_q_numba
4
+ from dynasor.core.rho_j_q_numba import rho_j_q as rho_j_q_numba
5
+
6
+
7
+ def calc_rho_q(x, q):
8
+ """Calculate rho(q) of particle coordinates x.
9
+
10
+ Will call external function rho_q to calculate the
11
+ particle density in q-space.
12
+ Particle coordinates and q-space points of interest are
13
+ passed as input via x and q, respectively.
14
+ """
15
+
16
+ assert x.shape[1] == 3
17
+ assert q.shape[1] == 3
18
+
19
+ Nx, _ = x.shape
20
+ Nq, _ = q.shape
21
+
22
+ rho_q = np.zeros(Nq, dtype=np.complex128)
23
+
24
+ x = x.copy() # Don't ask why
25
+ rho_q_numba(x, q, rho_q)
26
+
27
+ return rho_q
28
+
29
+
30
+ def calc_rho_j_q(x, v, q):
31
+ """As calc_rho_q, but calculate also velocities in q-space
32
+ """
33
+ assert x.shape == v.shape
34
+
35
+ assert x.shape[1] == 3
36
+ assert v.shape[1] == 3
37
+ assert q.shape[1] == 3
38
+
39
+ Nx, _ = x.shape
40
+ Nq, _ = q.shape
41
+
42
+ rho_q = np.zeros(Nq, dtype=np.complex128)
43
+ j_q = np.zeros((Nq, 3), dtype=np.complex128)
44
+
45
+ x = x.copy()
46
+ v = v.copy()
47
+
48
+ rho_j_q_numba(x, v, q, rho_q, j_q)
49
+
50
+ return rho_q, j_q
@@ -0,0 +1,127 @@
1
+ """This module replaces the original c implementation of the reciprocal
2
+ densities and currents in dynasor with numba.
3
+
4
+ Numba is as of 2023 an ongoing project to create a JIT compiler frontend for
5
+ python code using the LLVM project as backend. Due to current limitations and
6
+ quirks of numba the code is not always straightforward. Typically the code
7
+ needs to be refactored in a trial and error process to get the expected
8
+ performance but should in the end be on the level of c.
9
+
10
+ Especially, numba makes very pessimistic assumptions about aliasing but this is
11
+ expected to change in the future. Also, in theory, via the llvm-lite interface
12
+ compilation flags should be passable to LLVM.
13
+ """
14
+
15
+ import numpy as np
16
+ import numba
17
+
18
+
19
+ # This is often faster than calling np.dot for small arrays
20
+ # Calling this instead of manually inlining it actually incurs a small
21
+ # performance hit (<10%) with current numba (2023). It increases readability
22
+ # though and will probably sort itself out with later numba versions
23
+ @numba.njit(fastmath=True, nogil=True)
24
+ def dot(a, b):
25
+ return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]
26
+
27
+
28
+ # fastmath True makes the summation fast and also speeds up exponentiation
29
+ # nogil releases the python GIL, probably not neccesary here
30
+ @numba.njit(fastmath=True, nogil=True)
31
+ def rho_q_single(x: np.ndarray,
32
+ q: np.ndarray) -> complex:
33
+ """Calculates the density at a single q-point
34
+
35
+ Parameters
36
+ ----------
37
+ x
38
+ positions as a (N, 3) array
39
+ q
40
+ single q point as a with shape (3,)
41
+
42
+ Returns
43
+ -------
44
+ rho
45
+ complex density at the specified q-point
46
+ """
47
+ Nx = len(x)
48
+
49
+ assert x.shape == (Nx, 3)
50
+ assert q.shape == (3,)
51
+
52
+ rho = 0.0j
53
+ for i in range(Nx):
54
+ alpha = dot(x[i], q)
55
+ rho += np.exp(1j * alpha) # very expensive operation
56
+ return rho
57
+
58
+
59
+ # parallel enables the numba.prange directive
60
+ @numba.njit(fastmath=True, nogil=True, parallel=True)
61
+ def rho_q(x: np.ndarray, q: np.ndarray, rho: np.ndarray):
62
+ """Calculates the fourier transformed density
63
+
64
+ The parallelization is over q-points. The density is calculated in-place.
65
+
66
+ Parameters
67
+ ----------
68
+ x
69
+ the positions as a float array with shape (``Nx``, 3)
70
+ q
71
+ the q points as a float array with shape (``Nq``, 3)
72
+ rho
73
+ density as a complex array of length ``Nq``
74
+ """
75
+
76
+ Nx = len(x)
77
+ Nq = len(q)
78
+
79
+ assert x.shape == (Nx, 3)
80
+ assert q.shape == (Nq, 3)
81
+ assert rho.shape == (Nq,)
82
+
83
+ # Numba prange is like OMP
84
+ for i in numba.prange(Nq):
85
+ rho[i] = rho_q_single(x, q[i])
86
+
87
+
88
+ @numba.njit(fastmath=True, parallel=True, nogil=True)
89
+ def rho_j_q(x: np.ndarray, v: np.ndarray, q: np.ndarray,
90
+ rho: np.ndarray, j_q: np.ndarray):
91
+ """Calculates the fourier transformed density and current.
92
+
93
+ The output is stored in the supplied output arrays ``rho`` and ``j_q``
94
+
95
+ Parameters
96
+ ----------
97
+ x
98
+ the positions as a float array with shape (``Nx``, 3)
99
+ v
100
+ the velocities as a float array with shape (``Nx``, 3)
101
+ q
102
+ the q points as a float array with shape (``Nq``, 3)
103
+ rho
104
+ density as a complex array of length ``Nq``
105
+ j_q
106
+ current as a complex array with shape (``Nq``, 3)
107
+ """
108
+
109
+ Nx = len(x)
110
+ Nq = len(q)
111
+
112
+ assert x.shape == (Nx, 3)
113
+ assert v.shape == (Nx, 3)
114
+ assert q.shape == (Nq, 3)
115
+ assert rho.shape == (Nq,)
116
+ assert j_q.shape == (Nq, 3)
117
+
118
+ for qi in numba.prange(Nq):
119
+ for xi in range(Nx):
120
+
121
+ alpha = dot(x[xi], q[qi])
122
+ exp_ialpha = np.exp(1.0j * alpha)
123
+
124
+ rho[qi] += exp_ialpha
125
+
126
+ for i in range(3):
127
+ j_q[qi, i] += exp_ialpha * v[xi][i]
@@ -0,0 +1,44 @@
1
+ import numpy as np
2
+
3
+
4
+ class TimeAverager:
5
+ """Naive special purpose averager class used in dynasor to collect and time-average arrays
6
+ obtained from sliding time-window averaging.
7
+
8
+ It assists with keeping track of how many data samples have been added to each slot.
9
+
10
+ It will time-average arrays of shape ``(Nq, time_window)`` where ``Ǹq`` is the
11
+ number of q-points and ``time_window`` is the size of the time window.
12
+
13
+ Parameters
14
+ ----------
15
+ time_window
16
+ size of the time window in which the time-average happens
17
+ array_length
18
+ length of the array to be averaged for each time-lag, i.e. number of q-points
19
+ """
20
+
21
+ def __init__(self, time_window: int, array_length: int):
22
+ assert time_window >= 1
23
+ self._time_window = time_window
24
+ self._array_length = array_length
25
+
26
+ self._counts = np.zeros(time_window, dtype=int)
27
+ self._arrays = [np.zeros(array_length) for _ in range(time_window)]
28
+
29
+ def add_sample(self, time_lag: int, sample: np.ndarray):
30
+ assert len(sample) == self._array_length
31
+ self._counts[time_lag] += 1
32
+ self._arrays[time_lag] += sample
33
+
34
+ def get_average_at_timelag(self, time_lag: int):
35
+ if self._counts[time_lag] == 0:
36
+ array = np.full((self._array_length, ), np.nan)
37
+ return array
38
+ return self._arrays[time_lag] / self._counts[time_lag]
39
+
40
+ def get_average_all(self):
41
+ """
42
+ Returns an averaged array of shape ``(array_length, time_window)``
43
+ """
44
+ return np.array([self.get_average_at_timelag(t) for t in range(self._time_window)]).T