-
Notifications
You must be signed in to change notification settings - Fork 14
/
analysis.py
236 lines (179 loc) · 7.56 KB
/
analysis.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# AUTOGENERATED! DO NOT EDIT! File to edit: ../source_nbs/lib_nbs/analysis.ipynb.
# %% auto 0
__all__ = ['get_angle', 'dataset_angles', 'msd_analysis', 'vacf', 'CH_changepoints', 'CRLB_D']
# %% ../source_nbs/lib_nbs/analysis.ipynb 2
import numpy as np
import math
# %% ../source_nbs/lib_nbs/analysis.ipynb 5
def get_angle(a:tuple, # 2d position point A
b:tuple, # 2d position point B
c:tuple # 2d position point C
) -> tuple: # angle between segments AB and BC points
''' Calculates the angle between the segments generate by three points '''
ang = math.degrees(math.atan2(c[1]-b[1], c[0]-b[0]) - math.atan2(a[1]-b[1], a[0]-b[0]))
return ang + 360 if ang < 0 else ang
def dataset_angles(trajs:list, # set of trajectories from which to calculate angles
) -> list: # list of angles between displacements
'''Given a set of trajectories, calculate all angles between displacements'''
angles = []
for traj in trajs:
for a, b, c in zip(traj[:, :-2].transpose(), traj[:, 1:-1].transpose(), traj[:, 2:].transpose()):
angles.append(get_angle(a, b, c))
return angles
# %% ../source_nbs/lib_nbs/analysis.ipynb 7
class msd_analysis():
def __init__(self):
''' Contains mean squared displacement (MSD) based methods to analyze trajectories. '''
def tamsd(self,
trajs:np.ndarray,
t_lags:np.ndarray,
dim = 1
):
'''
Calculates the time average mean squared displacement (TA-MSD) of a trajectory at various time lags,
Parameters
----------
trajs : np.array
Set of trajectories of dimenions NxTxD (N: number of trajectories, T: lenght, D: dimension)
t_lags : list | np.array
Time lags used for the TA-MSD
dim : int
Dimension of the trajectories (currently only 1 and 2 supported)
Returns
-------
np.array
TA-MSD of each trayectory / t_lag
'''
tamsd = np.zeros((len(t_lags), trajs.shape[0]), dtype= float)
for idx, tlag in enumerate(t_lags):
tamsd[idx, :] = ((trajs[:, tlag:, :]-trajs[:, :-tlag, :])**2).sum(-1).mean(1)
return tamsd
def get_diff_coeff(self,
trajs:np.ndarray,
t_lags:list = None):
'''
Calculates the diffusion coefficient of a trajectory by means of the linear
fitting of the TA-MSD.
Parameters
----------
traj : np.array
Set of trajectories of dimenions NxTxD (N: number of trajectories, T: lenght, D: dimension)
t_lags : bool | list
Time lags used for the TA-MSD.
Returns
-------
np.array
Diffusion coefficient of the given trajectory.
'''
# To account for previous versions of this function, we correct if given a single 1D trajectory
if len(trajs.shape) == 1:
trajs = trajs[np.newaxis, :, np.newaxis]
if not t_lags:
N_t_lags = max(4, int(trajs.shape[1]*0.1))
t_lags = np.arange(1, N_t_lags)
tasmd = self.tamsd(trajs, t_lags)
return np.polyfit(t_lags, tasmd, deg = 1)[0, :]/2/trajs.shape[-1]
def get_exponent(self,
trajs:np.ndarray,
t_lags:list = None):
'''
Calculates the diffusion coefficient of a trajectory by means of the linear
fitting of the TA-MSD.
Parameters
----------
traj : np.array
Set of trajectories of dimenions NxTxD (N: number of trajectories, T: lenght, D: dimension)
t_lags : bool | list
Time lags used for the TA-MSD.
Returns
-------
np.array
Diffusion coefficient of the given trajectory.
'''
# To account for previous versions of this function, we correct if given a single 1D trajectory
if len(trajs.shape) == 1:
trajs = trajs[np.newaxis, :, np.newaxis]
if not t_lags:
N_t_lags = max(4, int(trajs.shape[1]*0.1))
t_lags = np.arange(1, N_t_lags)
tasmd = self.tamsd(trajs, t_lags)
return np.polyfit(np.log(t_lags), np.log(tasmd), deg = 1)[0]
# %% ../source_nbs/lib_nbs/analysis.ipynb 16
def vacf(trajs,
delta_t:int | list | np.ndarray = 1,
taus:bool | list | np.ndarray = None):
'''
Calculates the velocity autocorrelation function for
the given set of trajectories.
Parameters
----------
trajs : np.array
NxT matrix containing N trajectories of length T.
delta_t : int | list | array
If not None, the vacf is calculated in the demanded time lags.
taus : bool | list | array
Time windows at wich the vacf is calculated.
Returns
-------
np.array
VACF of the given trajectories and the given time windows.
'''
if isinstance(delta_t, int): delta_t = [delta_t]
if taus is None: taus = np.arange(1, trajs.shape[1]).astype(int)
V = np.zeros((len(delta_t), len(taus)))
for idx_d, delta in enumerate(delta_t):
# Calculate the velocity
velocity = trajs[: ,delta:] - trajs[:,:-delta]
velocity /= delta_t
for idx_t, tau in enumerate(taus):
if tau == 0:
V[idx_d, idx_t] = (velocity**2).mean()
else:
V[idx_d, idx_t] = (velocity[:, :-tau]*velocity[:, tau:]).mean()
V[idx_d, :] /= V[idx_d, 0]
return V
# %% ../source_nbs/lib_nbs/analysis.ipynb 20
from scipy.spatial import ConvexHull
def CH_changepoints(trajs,
tau:int = 10,
metric:{'volume', 'area'} = 'volume'):
'''
Computes the changes points a multistate trajectory based on the Convex Hull approach proposed in PRE 96 (022144), 2017.
Parameters
----------
trajs : np.array
NxT matrix containing N trajectories of length T.
tau : int
Time window over which the CH is calculated.
metric : {'volume', 'area'}
Calculate change points w.r.t. area or volume of CH.
Returns
-------
list
Change points of the given trajectory.
'''
CPs = []
for traj in trajs:
traj = np.array(traj)
Sd = np.zeros(traj.shape[0]-2*tau)
for k in range(traj.shape[0]-2*tau):
if metric == 'volume':
Sd[k] = ConvexHull(traj[k:(k+2*tau)]).volume
elif metric == 'area':
Sd[k] = ConvexHull(traj[k:(k+2*tau)]).area
below_mean = Sd < Sd.mean()
cp_traj = np.argwhere(below_mean[1:] != below_mean[:-1])+1
CPs.append(cp_traj+tau)
return CPs
# %% ../source_nbs/lib_nbs/analysis.ipynb 24
def CRLB_D(T:int, # Length of the trajectory
dim:int = 1 # Dimension of the trajectoy
) ->float: # Cramér-Rao bound
'''
Calculates the bound for S(D)/D, i.e. ratio between the standard deviation and the expected value of D
This holds for x->0 only (i.e. no noise)! See PRE 85, 061916 (2012) for full equation.
'''
if dim == 1:
return 2*((4-3*T)/((4-2*T)*(T-1)))**(1/2)
if dim == 2:
return ((3*T-4)/((T-1)*(T-2)))**(1/2)