vbi 0.1.3__cp310-cp310-manylinux2014_x86_64.whl → 0.2__cp310-cp310-manylinux2014_x86_64.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.
- vbi/feature_extraction/features.json +4 -1
- vbi/feature_extraction/features.py +10 -4
- vbi/inference.py +50 -22
- vbi/models/cpp/_src/_do.cpython-310-x86_64-linux-gnu.so +0 -0
- vbi/models/cpp/_src/_jr_sdde.cpython-310-x86_64-linux-gnu.so +0 -0
- vbi/models/cpp/_src/_jr_sde.cpython-310-x86_64-linux-gnu.so +0 -0
- vbi/models/cpp/_src/_km_sde.cpython-310-x86_64-linux-gnu.so +0 -0
- vbi/models/cpp/_src/_mpr_sde.cpython-310-x86_64-linux-gnu.so +0 -0
- vbi/models/cpp/_src/_vep.cpython-310-x86_64-linux-gnu.so +0 -0
- vbi/models/cpp/_src/_wc_ode.cpython-310-x86_64-linux-gnu.so +0 -0
- vbi/models/cpp/_src/jr_sde.hpp +5 -6
- vbi/models/cpp/_src/jr_sde_wrap.cxx +28 -28
- vbi/models/cpp/jansen_rit.py +2 -9
- vbi/models/cupy/bold.py +117 -0
- vbi/models/cupy/jansen_rit.py +1 -1
- vbi/models/cupy/km.py +62 -34
- vbi/models/cupy/mpr.py +24 -4
- vbi/models/cupy/utils.py +163 -2
- vbi/models/cupy/wilson_cowan.py +317 -0
- vbi/models/cupy/ww.py +342 -0
- vbi/models/numba/__init__.py +4 -0
- vbi/models/numba/jansen_rit.py +532 -0
- vbi/models/numba/mpr.py +8 -0
- vbi/models/numba/wilson_cowan.py +443 -0
- vbi/models/numba/ww.py +564 -0
- {vbi-0.1.3.dist-info → vbi-0.2.dist-info}/METADATA +30 -11
- {vbi-0.1.3.dist-info → vbi-0.2.dist-info}/RECORD +30 -26
- {vbi-0.1.3.dist-info → vbi-0.2.dist-info}/WHEEL +1 -1
- vbi/models/numba/_ww_EI.py +0 -444
- {vbi-0.1.3.dist-info → vbi-0.2.dist-info}/licenses/LICENSE +0 -0
- {vbi-0.1.3.dist-info → vbi-0.2.dist-info}/top_level.txt +0 -0
vbi/models/cupy/km.py
CHANGED
@@ -4,40 +4,61 @@ from vbi.models.cupy.utils import *
|
|
4
4
|
|
5
5
|
|
6
6
|
class KM_sde:
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
7
|
+
|
8
|
+
"""
|
9
|
+
Kuramoto model with noise (stochastic differential equation)
|
10
|
+
|
11
|
+
Parameters
|
12
|
+
----------
|
13
|
+
G: float
|
14
|
+
global coupling strength
|
15
|
+
dt: float
|
16
|
+
time step
|
17
|
+
noise_amp: float
|
18
|
+
noise amplitude
|
19
|
+
weights: array
|
20
|
+
weighted connection matrix
|
21
|
+
omega: array
|
22
|
+
natural angular frequency
|
23
|
+
seed: int
|
24
|
+
fix random seed for initial state
|
25
|
+
t_cut: float
|
26
|
+
transition time
|
27
|
+
t_end: float
|
28
|
+
end time
|
29
|
+
decimate: int
|
30
|
+
decimate the output time series
|
31
|
+
output: str
|
32
|
+
output directory
|
33
|
+
initial_state: array
|
34
|
+
initial state
|
35
|
+
engine: str
|
36
|
+
cpu or gpu
|
37
|
+
type: type
|
38
|
+
data type for calculations, default is np.float32
|
39
|
+
alpha: array # TODO not implemented
|
40
|
+
frustration matrix
|
41
|
+
num_sim: int
|
42
|
+
number of simulations
|
43
|
+
method: str
|
44
|
+
integration method, default is heun
|
45
|
+
same_initial_state: bool
|
46
|
+
use the same initial state for all simulations, default is False
|
47
|
+
|
48
|
+
"""
|
26
49
|
|
27
50
|
def __init__(self, par={}) -> None:
|
28
51
|
|
29
|
-
self.check_parameters(par)
|
30
52
|
self._par = self.get_default_parameters()
|
53
|
+
self.valid_parameters = list(self._par.keys())
|
54
|
+
self.check_parameters(par)
|
31
55
|
self._par.update(par)
|
32
56
|
|
33
57
|
for item in self._par.items():
|
34
|
-
|
35
|
-
value = item[1]
|
36
|
-
setattr(self, name, value)
|
58
|
+
setattr(self, item[0], item[1])
|
37
59
|
|
38
60
|
self.xp = get_module(self.engine)
|
39
61
|
self.ns = self.num_sim
|
40
|
-
|
41
62
|
self.nn = self.num_nodes = self.weights.shape[0]
|
42
63
|
|
43
64
|
if self.seed is not None:
|
@@ -55,9 +76,7 @@ class KM_sde:
|
|
55
76
|
print (f"Kuramoto model with noise (sde), {self.engine} implementation.")
|
56
77
|
print ("----------------")
|
57
78
|
for item in self._par.items():
|
58
|
-
|
59
|
-
value = item[1]
|
60
|
-
print (f"{name} = {value}")
|
79
|
+
print (f"{item[0]} = {item[1]}")
|
61
80
|
return ""
|
62
81
|
|
63
82
|
def __call__(self):
|
@@ -74,18 +93,18 @@ class KM_sde:
|
|
74
93
|
"weights": None, # weighted connection matrix
|
75
94
|
"omega": None, # natural angular frequency
|
76
95
|
"seed": None, # fix random seed for initial state
|
77
|
-
"
|
78
|
-
"
|
96
|
+
"t_start": 0.0, # initial time
|
97
|
+
"t_cut": 0.0, # transition time
|
79
98
|
"t_end": 100.0, # end time
|
80
|
-
"num_threads": 1, # number of threads using openmp
|
81
99
|
"output": "output", # output directory
|
82
100
|
"initial_state": None, # initial state
|
83
101
|
"engine": "cpu", # cpu or gpu
|
84
102
|
"type": np.float32, # output times series data type
|
85
|
-
"alpha": None, # frustration matrix
|
103
|
+
"alpha": None, # frustration matrix # TODO not implemented
|
86
104
|
"num_sim": 1, # number of simulations
|
87
105
|
"method": "heun", # integration method
|
88
106
|
"same_initial_state": False, # use the same initial state for all simulations
|
107
|
+
"decimate": 1, # decimate the output time series
|
89
108
|
|
90
109
|
}
|
91
110
|
|
@@ -130,12 +149,12 @@ class KM_sde:
|
|
130
149
|
x = self.initial_state
|
131
150
|
xs = []
|
132
151
|
integrator = self.euler if self.method == "euler" else self.heun
|
133
|
-
n_transition = int(self.
|
134
|
-
self.dt) if self.
|
152
|
+
n_transition = int(self.t_cut /
|
153
|
+
self.dt) if self.t_cut > 0 else 1
|
135
154
|
|
136
155
|
for it in tqdm.tqdm(range(1, len(t)), disable=not verbose, desc="Integrating"):
|
137
156
|
x = integrator(x, t[it])
|
138
|
-
if it >= n_transition:
|
157
|
+
if (it >= n_transition) and (it % self.decimate == 0):
|
139
158
|
if self.engine == "gpu":
|
140
159
|
xs.append(x.get())
|
141
160
|
else:
|
@@ -144,6 +163,15 @@ class KM_sde:
|
|
144
163
|
t = t[n_transition:]
|
145
164
|
|
146
165
|
return {"t": t, "x": xs}
|
166
|
+
|
167
|
+
def step(self, x, t):
|
168
|
+
''' Step function for the model'''
|
169
|
+
if self.method == "euler":
|
170
|
+
return self.euler(x, t)
|
171
|
+
elif self.method == "heun":
|
172
|
+
return self.heun(x, t)
|
173
|
+
else:
|
174
|
+
raise ValueError(f"Invalid method: {self.method}")
|
147
175
|
|
148
176
|
def run(self, x0=None, verbose=True):
|
149
177
|
'''
|
vbi/models/cupy/mpr.py
CHANGED
@@ -150,7 +150,7 @@ class MPR_sde:
|
|
150
150
|
model parameter
|
151
151
|
tr: float
|
152
152
|
repetition time of fMRI
|
153
|
-
noise_amp: float
|
153
|
+
noise_amp: float, np.array
|
154
154
|
amplitude of noise
|
155
155
|
same_noise_per_sim:
|
156
156
|
same noise for all simulations
|
@@ -200,6 +200,8 @@ class MPR_sde:
|
|
200
200
|
name = item[0]
|
201
201
|
value = item[1]
|
202
202
|
setattr(self, name, value)
|
203
|
+
|
204
|
+
self.update_dependent_parameters()
|
203
205
|
|
204
206
|
self.B = Bold(Bpar)
|
205
207
|
|
@@ -266,7 +268,7 @@ class MPR_sde:
|
|
266
268
|
"same_initial_state": False,
|
267
269
|
}
|
268
270
|
dt = par["dt"]
|
269
|
-
noise_amp = par["noise_amp"]
|
271
|
+
noise_amp = np.array(par["noise_amp"])
|
270
272
|
sigma_r = np.sqrt(dt) * np.sqrt(2 * noise_amp)
|
271
273
|
sigma_v = np.sqrt(dt) * np.sqrt(4 * noise_amp)
|
272
274
|
par["sigma_r"] = sigma_r
|
@@ -274,17 +276,35 @@ class MPR_sde:
|
|
274
276
|
# par.update(self.get_balloon_parameters())
|
275
277
|
|
276
278
|
return par
|
279
|
+
|
280
|
+
def update_dependent_parameters(self):
|
281
|
+
dt = self.dt
|
282
|
+
noise_amp = self.noise_amp
|
283
|
+
if hasattr(noise_amp, "__iter__"):
|
284
|
+
noise_amp = np.array(noise_amp)
|
285
|
+
else:
|
286
|
+
noise_amp = np.array([noise_amp])
|
287
|
+
|
288
|
+
sigma_r = np.sqrt(dt) * np.sqrt(2 * noise_amp)
|
289
|
+
sigma_v = np.sqrt(dt) * np.sqrt(4 * noise_amp)
|
290
|
+
self.sigma_r = sigma_r
|
291
|
+
self.sigma_v = sigma_v
|
292
|
+
self._par["sigma_r"] = sigma_r
|
293
|
+
self._par["sigma_v"] = sigma_v
|
294
|
+
|
277
295
|
|
278
296
|
def check_parameters(self, par):
|
279
297
|
for key in par.keys():
|
280
298
|
if key not in self.valid_parameters:
|
281
299
|
raise ValueError(f"Invalid parameter {key:s} provided.")
|
282
|
-
|
300
|
+
|
283
301
|
def prepare_input(self):
|
284
302
|
|
285
303
|
self.G = self.xp.array(self.G)
|
304
|
+
self.sigma_r = self.xp.array(self.sigma_r)
|
305
|
+
self.sigma_v = self.xp.array(self.sigma_v)
|
286
306
|
assert self.weights is not None, "weights must be provided"
|
287
|
-
self.weights = self.xp.array(self.weights)
|
307
|
+
self.weights = self.xp.array(self.weights)
|
288
308
|
self.weights = move_data(self.weights, self.engine)
|
289
309
|
self.nn = self.num_nodes = self.weights.shape[0]
|
290
310
|
|
vbi/models/cupy/utils.py
CHANGED
@@ -75,6 +75,47 @@ def todevice(x):
|
|
75
75
|
'''
|
76
76
|
return cp.asarray(x)
|
77
77
|
|
78
|
+
# write a function to detexct where is x on cpu or gpu
|
79
|
+
|
80
|
+
def is_on_cpu(x):
|
81
|
+
'''
|
82
|
+
Check if input is on CPU (i.e., not a CuPy array)
|
83
|
+
|
84
|
+
Parameters
|
85
|
+
----------
|
86
|
+
x: any
|
87
|
+
Input to check
|
88
|
+
|
89
|
+
Returns
|
90
|
+
-------
|
91
|
+
bool
|
92
|
+
True if input is not a CuPy array (i.e., is on CPU), False otherwise
|
93
|
+
'''
|
94
|
+
if cp is None:
|
95
|
+
return None
|
96
|
+
return not isinstance(x, cp.ndarray)
|
97
|
+
|
98
|
+
def is_on_gpu(x):
|
99
|
+
'''
|
100
|
+
Check if input is on GPU (i.e., is a CuPy array)
|
101
|
+
|
102
|
+
Parameters
|
103
|
+
----------
|
104
|
+
x: any
|
105
|
+
Input to check
|
106
|
+
|
107
|
+
Returns
|
108
|
+
-------
|
109
|
+
bool or None
|
110
|
+
True if input is a CuPy array (i.e., is on GPU)
|
111
|
+
False if input is not a CuPy array
|
112
|
+
None if CuPy is not installed
|
113
|
+
'''
|
114
|
+
if cp is None:
|
115
|
+
return None
|
116
|
+
return isinstance(x, cp.ndarray)
|
117
|
+
|
118
|
+
|
78
119
|
|
79
120
|
def move_data(x, engine):
|
80
121
|
if engine == "cpu":
|
@@ -124,6 +165,10 @@ def is_seq(x):
|
|
124
165
|
'''
|
125
166
|
return hasattr(x, '__iter__')
|
126
167
|
|
168
|
+
# def is_seq(x):
|
169
|
+
# """Check if x is a sequence (list, tuple, array) but not a string"""
|
170
|
+
# return hasattr(x, '__len__') and not isinstance(x, (str, bytes))
|
171
|
+
|
127
172
|
|
128
173
|
def prepare_vec(x, ns, engine, dtype="float"):
|
129
174
|
'''
|
@@ -137,6 +182,8 @@ def prepare_vec(x, ns, engine, dtype="float"):
|
|
137
182
|
number of simulations
|
138
183
|
engine: str
|
139
184
|
cpu or gpu
|
185
|
+
dtype: str or numpy.dtype
|
186
|
+
data type to convert to
|
140
187
|
|
141
188
|
Returns
|
142
189
|
-------
|
@@ -145,9 +192,18 @@ def prepare_vec(x, ns, engine, dtype="float"):
|
|
145
192
|
|
146
193
|
'''
|
147
194
|
xp = get_module(engine)
|
148
|
-
|
149
195
|
if not is_seq(x):
|
150
|
-
|
196
|
+
# Convert dtype string to numpy dtype
|
197
|
+
if isinstance(dtype, str):
|
198
|
+
if dtype == "float":
|
199
|
+
dtype = np.float64
|
200
|
+
elif dtype == "float32":
|
201
|
+
dtype = np.float32
|
202
|
+
elif dtype == "float64":
|
203
|
+
dtype = np.float64
|
204
|
+
else:
|
205
|
+
raise ValueError(f"Unsupported dtype: {dtype}")
|
206
|
+
x = xp.array(x, dtype=dtype)
|
151
207
|
else:
|
152
208
|
x = np.array(x)
|
153
209
|
if x.ndim == 1:
|
@@ -159,7 +215,31 @@ def prepare_vec(x, ns, engine, dtype="float"):
|
|
159
215
|
raise ValueError("x.ndim must be 1 or 2")
|
160
216
|
return x.astype(dtype)
|
161
217
|
|
218
|
+
def prepare_vec_1d(x, ns, engine, dtype="float"):
|
219
|
+
'''
|
220
|
+
check and prepare vector dimension and type
|
221
|
+
'''
|
222
|
+
if not is_seq(x):
|
223
|
+
# Convert dtype string to numpy dtype
|
224
|
+
if isinstance(dtype, str):
|
225
|
+
if dtype == "float":
|
226
|
+
dtype = np.float64
|
227
|
+
elif dtype == "float32":
|
228
|
+
dtype = np.float32
|
229
|
+
elif dtype == "float64":
|
230
|
+
dtype = np.float64
|
231
|
+
else:
|
232
|
+
raise ValueError(f"Unsupported dtype: {dtype}")
|
233
|
+
x = np.array(x, dtype=dtype)
|
234
|
+
else:
|
235
|
+
assert(x.ndim == 1), "x must be a 1d array"
|
236
|
+
assert(x.shape[0] == ns), "x must have the same number of elements as ns"
|
237
|
+
x = move_data(x, engine)
|
238
|
+
return x.astype(dtype)
|
162
239
|
|
240
|
+
|
241
|
+
|
242
|
+
|
163
243
|
def get_(x, engine="cpu", dtype="f"):
|
164
244
|
"""
|
165
245
|
Parameters
|
@@ -182,3 +262,84 @@ def get_(x, engine="cpu", dtype="f"):
|
|
182
262
|
return x.get().astype(dtype)
|
183
263
|
else:
|
184
264
|
return x.astype(dtype)
|
265
|
+
|
266
|
+
|
267
|
+
def dtype_convert(dtype):
|
268
|
+
"""
|
269
|
+
Convert a string representation of a data type to a numpy dtype.
|
270
|
+
|
271
|
+
Parameters
|
272
|
+
----------
|
273
|
+
dtype : str
|
274
|
+
The string representation of the data type (e.g., "float", "float32", "float64").
|
275
|
+
|
276
|
+
Returns
|
277
|
+
-------
|
278
|
+
numpy.dtype
|
279
|
+
The corresponding numpy dtype.
|
280
|
+
|
281
|
+
Raises
|
282
|
+
------
|
283
|
+
ValueError
|
284
|
+
If the input string does not match any known data type.
|
285
|
+
"""
|
286
|
+
|
287
|
+
if dtype == "float":
|
288
|
+
return np.float64
|
289
|
+
elif dtype == "f":
|
290
|
+
return np.float32
|
291
|
+
elif dtype == "float32":
|
292
|
+
return np.float32
|
293
|
+
elif dtype == "float64":
|
294
|
+
return np.float64
|
295
|
+
else:
|
296
|
+
raise ValueError(f"Unsupported dtype: {dtype}")
|
297
|
+
|
298
|
+
|
299
|
+
def prepare_vec_2d(x, nn, ns, engine, dtype="float"):
|
300
|
+
'''
|
301
|
+
if x is scalar pass
|
302
|
+
if x is 1d array, shape should be (ns,)
|
303
|
+
if x is 2d array, shape should be (nn, ns)
|
304
|
+
'''
|
305
|
+
import numpy as np
|
306
|
+
|
307
|
+
# Determine target numpy dtype
|
308
|
+
try:
|
309
|
+
target_dtype = dtype_convert(dtype)
|
310
|
+
except ValueError:
|
311
|
+
target_dtype = np.dtype(dtype)
|
312
|
+
|
313
|
+
if not is_seq(x):
|
314
|
+
# x is scalar - return
|
315
|
+
return x
|
316
|
+
|
317
|
+
# Convert x to numpy array if it isn't already
|
318
|
+
x = np.asarray(x)
|
319
|
+
|
320
|
+
if x.ndim == 1:
|
321
|
+
# 1D array - should have shape (ns,)
|
322
|
+
if x.shape[0] != ns:
|
323
|
+
raise ValueError(f"1D array should have shape ({ns},), got {x.shape}")
|
324
|
+
|
325
|
+
# Change dtype
|
326
|
+
x = x.astype(target_dtype)
|
327
|
+
|
328
|
+
# Move to appropriate device
|
329
|
+
x = move_data(x, engine)
|
330
|
+
|
331
|
+
elif x.ndim == 2:
|
332
|
+
# 2D array - should have shape (nn, ns)
|
333
|
+
if x.shape != (nn, ns):
|
334
|
+
raise ValueError(f"2D array should have shape ({nn}, {ns}), got {x.shape}")
|
335
|
+
|
336
|
+
# Change dtype
|
337
|
+
x = x.astype(target_dtype)
|
338
|
+
|
339
|
+
# Move to appropriate device
|
340
|
+
x = move_data(x, engine)
|
341
|
+
|
342
|
+
else:
|
343
|
+
raise ValueError(f"Array should be 1D or 2D, got {x.ndim}D")
|
344
|
+
|
345
|
+
return x
|