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/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
- valid_parameters = [
9
- "num_sim", # number of simulations
10
- "G", # global coupling strength
11
- "dt", # time step
12
- "noise_amp", # noise amplitude
13
- "omega", # natural angular frequency
14
- "weights", # weighted connection matrix
15
- "seed",
16
- "alpha", # frustration matrix
17
- "t_initial", # initial time
18
- "t_transition", # transition time
19
- "t_end", # end time
20
- "output", # output directory
21
- "num_threads", # number of threads using openmp
22
- "initial_state",
23
- "type", # output times series data type
24
- "engine", # cpu or gpu
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
- name = item[0]
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
- name = item[0]
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
- "t_initial": 0.0, # initial time
78
- "t_transition": 0.0, # transition time
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.t_transition /
134
- self.dt) if self.t_transition > 0 else 1
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).T # ! Directed network #!TODO: check
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
- return eval(f"{dtype}({x})")
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