OSCR

Neuromorphic hierarchical modular reservoirs.

Code ↔ Paper

16 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 16 matches
  1. [1] § Methods › Data acquisition and connectome reconstruction ↔ netneurotools/networks/consensus.py, lines 134–215 · score 0.94 · cumulative edge length, breaking ties, consensus structural, inter hemispheric, distance dependent, edge length distribution
  2. [2] § Methods › Intrinsic timescales from magnetoencephalography (MEG) ↔ netneurotools/datasets/fetch_atlas.py, lines 138–261 · score 0.67 · fslr32k, Human Connectome, spectra, atlas, space, HCP
  3. [3] § Methods › Intrinsic timescales from magnetoencephalography (MEG) ↔ netneurotools/datasets/fetch_template.py, lines 1449–1558 · score 0.64 · cortical surface, fsLR32k, scans, space, atlas, downloaded
  4. [4] § Methods › Hyperparameter tuning ↔ task.py, lines 21–86 · score 0.63 · pruning ratio, spectral radius, L2, hyperparameters, Ridge, density
  5. [5] § Methods › Reservoir computing › Stability ↔ examples/example4_sims.py, lines 45–85 · score 0.61 · spectral radius, parametrically tune, Reservoir dynamics, global, weight, matrix
  6. [6] § Methods › Reservoir computing › Stability ↔ task.py, lines 21–86 · score 0.61 · Lyapunov exponent, spectral radius, trajectories, dynamics, weight, matrix
  7. [7] § Methods › Data acquisition and connectome reconstruction ↔ netneurotools/networks/consensus.py, lines 134–215 · score 0.61 · fractional anisotropy, streamline, structural connectivity, algorithm, weighted, networks
  8. [8] § Methods › Data acquisition and connectome reconstruction ↔ netneurotools/datasets/fetch_template.py, lines 543–681 · score 0.60 · minimal preprocessing pipelines, spherical, HCP, connectivity
  9. [9] § Methods › Graph analysis › Modularity maximization ↔ netneurotools/modularity/modules.py, lines 459–514 · score 0.57 · modularity maximization, community assignment, Louvain, algorithm, partitions, weight
  10. [10] § Results › Hierarchical modularity improves memory capacity ↔ examples/example4_sims.py, lines 45–85 · score 0.57 · spectral radius, parametrically tune, Reservoir dynamics, global, network
  11. [11] § Results › Hierarchical modularity in the human connectome ↔ empirical_timescales.ipynb, lines 145–159 · score 0.56 · empirical correlation, empirical timescales, Spearman correlation
  12. [12] § Methods › Multitasking ↔ task.py, lines 1306–1378 · score 0.54 · transformation task, memory capacity task, readout module, multitasking, cycles, seeds
  13. [13] § Methods › Network null models › Degree-preserving rewiring ↔ netneurotools/networks/randomize.py, lines 100–157 · score 0.54 · degree sequence, randomize network, surrogates, rewiring, edges, connected
  14. [14] § Methods › Memory capacity ↔ conn2res/tasks.py, lines 331–469 · score 0.52 · memory capacity task, uniformly distributed, delayed, reproduce, trained, signal
  15. [15] § Methods › Graph analysis › Modularity maximization ↔ network.py, lines 242–302 · score 0.52 · modularity maximization, community assignment, strength, hierarchical modular, empirical, nodes
  16. [16] § Methods › Network null models › Cycles-preserving null model ↔ supp_nulls.py, lines 377–438 · score 0.52 · simulated annealing, modularity preserving, cycle, rewiring, edges, modules

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Python · 1,706 lines · 66 KB · BSD-3-Clause · 3 matches

  1. from abc import ABC
  2. import numpy as np
  3. from scipy import signal
  4. import pandas as pd
  5. import bct
  6. import networkx as nx
  7. from conn2res.connectivity import Conn
  8. from conn2res.reservoir import EchoStateNetwork
  9. from conn2res.readout import Readout, _get_sample_weight
  10. from conn2res.tasks import Conn2ResTask, ReservoirPyTask, NeuroGymTask
  11. from sklearn.linear_model import Ridge, RidgeClassifier
  12. import os
  13. import pickle
  14. class Task(ABC):
  15. """
  16. Abstract base class for running tasks and hyperparameter optimization
  17. Attributes
  18. ----------
  19. duration : int
  20. Number of time steps on which the readout is trained
  21. warmup : int
  22. Number of initial time steps to discard before training the readout
  23. total_duration : int
  24. Total number of time steps in the task
  25. alphas : list
  26. List of reservoir spectral radii to test
  27. l2_alpha : float
  28. Ridge regularization parameter
  29. input_gain : float
  30. Input gain for the reservoir
  31. pruning_ratio : float
  32. Ratio of connections to prune
  33. compute_LE : bool
  34. Whether to compute the Lyapunov exponents
  35. alphas_to_save : list
  36. List of spectral radii for which to save the reservoir states
  37. criticality : float
  38. Critical spectral radius
  39. activation : str
  40. Activation function for the reservoir
  41. score : str
  42. Scoring method for the readout
  43. multioutput : str
  44. Multioutput strategy for the readout
  45. rs_path : str
  46. Path to save reservoir states
  47. LE_path : str
  48. Path to save Lyapunov exponents
  49. perform_path : str
  50. Path to save performance results
  51. Methods
  52. -------
  53. init_conn(seed, nets, level)
  54. Initialize a connectivity matrix
  55. init_net_id(level, module=None)
  56. Initialize a network ID
  57. simulation(alpha, conn, w_in, output_nodes, compute_LE,
  58. sample_weight, multioutput, task, readout_modules=None)
  59. Run a simulation
  60. task_workflow(conn, w_in, output_nodes, readout_modules, net_id=None,
  61. compute_LE=False, sample_weight=None,
  62. multioutput='uniform_average', task='MC')
  63. Run a task
  64. alpha_to_regime(alpha)
  65. Return the dynamical regime of the reservoir
  66. save_rs(esn, alpha, net_id)
  67. Save reservoir states
  68. save_LEs(LEs, LEs_trajectory, alpha, net_id)
  69. Save Lyapunov exponents
  70. save_results()
  71. Save performance results
  72. best_hyperparameter(niter, nseeds, aggregate='average',
  73. gain_opt=False, ridge_opt=False,
  74. density_opt=False)
  75. Return the best hyperparameters
  76. """
  77. def __init__(self, config):
  78. self.duration = config.duration
  79. self.warmup = config.warmup
  80. self.total_duration = self.duration + self.warmup
  81. self.alphas = config.alphas
  82. self.l2_alpha = config.l2_alpha
  83. self.input_gain = config.input_gain
  84. self.pruning_ratio = config.pruning_ratio
  85. self.compute_LE = config.compute_LE
  86. self.alphas_to_save = config.alphas_to_save
  87. self.criticality = config.criticality
  88. self.activation = config.activation
  89. self.score = config.score
  90. self.multioutput = config.multioutput
  91. self.rs_path = config.rs_path
  92. self.LE_path = config.LE_path
  93. self.perform_path = config.perform_path
  94. def init_conn(self, seed, nets, level):
  95. w = np.array(nets[level][seed])
  96. #delete self.pruning_ratio of connections
  97. if self.pruning_ratio > 0:
  98. #directed
  99. if not np.allclose(w, w.T):
  100. idx = np.where(w != 0)
  101. nedges = len(idx[0])
  102. nprune = int(self.pruning_ratio*nedges)
  103. idx_prune = np.random.choice(range(nedges),
  104. nprune, replace=False)
  105. w[idx[0][idx_prune], idx[1][idx_prune]] = 0
  106. #check connectedness
  107. if not nx.is_strongly_connected(nx.DiGraph(w)):
  108. return None
  109. #undirected
  110. else:
  111. #upper triangle
  112. triu_w = np.triu(w)
  113. idx = np.where(triu_w != 0)
  114. nedges = len(idx[0])
  115. nprune = int(self.pruning_ratio*nedges)
  116. idx_prune = np.random.choice(range(nedges),
  117. nprune, replace=False)
  118. triu_w[idx[0][idx_prune], idx[1][idx_prune]] = 0
  119. #add back the lower triangle
  120. w = triu_w + np.tril(triu_w.T, -1)
  121. #check connectedness
  122. if bct.number_of_components(w) > 1:
  123. return None
  124. conn = Conn(w=w)
  125. #normalize the connectivity matrix to have a spectral radius of 1
  126. conn.normalize()
  127. return conn
  128. def init_net_id(self, level, module=None):
  129. if module is not None:
  130. net_id = '_level{}_module{}_{}.npy'.format(level, module,
  131. self.seed)
  132. else:
  133. net_id = '_level{}_{}.npy'.format(level, self.seed)
  134. return net_id
  135. def simulation(self, alpha, conn, w_in,
  136. output_nodes, compute_LE,
  137. multioutput, task,
  138. readout_modules=None,
  139. sample_weight=None):
  140. esn = EchoStateNetwork(w=alpha*conn.w,
  141. activation_function=self.activation)
  142. rs_train = esn.simulate(
  143. ext_input=self.x_train, w_in=w_in, input_gain=self.input_gain,
  144. output_nodes=output_nodes, compute_LE=False, warmup=self.warmup
  145. )
  146. rs_train = rs_train[self.warmup:]
  147. rs_test = esn.simulate(
  148. ext_input=self.x_test, w_in=w_in, input_gain=self.input_gain,
  149. output_nodes=output_nodes, compute_LE=compute_LE,
  150. warmup=self.warmup
  151. )
  152. rs_test = rs_test[self.warmup:]
  153. if task != 'MT':
  154. if self.score == 'corrcoef':
  155. df_res = self.readout.run_task(
  156. X=(rs_train, rs_test), y=(self.y_train, self.y_test),
  157. sample_weight=sample_weight, metric=self.score,
  158. readout_modules=readout_modules, multioutput=multioutput,
  159. nonnegative='squared'
  160. )
  161. else:
  162. df_res = self.readout.run_task(
  163. X=(rs_train, rs_test), y=(self.y_train, self.y_test),
  164. sample_weight=sample_weight, metric=self.score,
  165. readout_modules=readout_modules, multioutput=multioutput
  166. )
  167. if task == 'MT':
  168. return esn, rs_train, rs_test
  169. else:
  170. return esn, df_res
  171. def task_workflow(self, conn, w_in,
  172. output_nodes, readout_modules,
  173. net_id=None, compute_LE=False,
  174. sample_weight=None,
  175. multioutput='uniform_average',
  176. task='single'):
  177. df_alpha = []
  178. for alpha in self.alphas:
  179. esn, df_res = self.simulation(alpha, conn, w_in,
  180. output_nodes, compute_LE,
  181. multioutput, task,
  182. readout_modules=readout_modules,
  183. sample_weight=sample_weight)
  184. if net_id is not None:
  185. self.save_rs(esn, alpha, net_id)
  186. if compute_LE:
  187. self.save_LEs(esn, alpha, net_id)
  188. df_res['alpha'] = alpha
  189. df_alpha.append(df_res)
  190. df_alpha = pd.concat(df_alpha, ignore_index=True)
  191. return df_alpha
  192. def alpha_to_regime(self, alpha):
  193. if alpha < self.criticality:
  194. return 'stable'
  195. elif alpha == self.criticality:
  196. return 'critical'
  197. else:
  198. return 'chaotic'
  199. def save_rs(self, esn, alpha, net_id):
  200. if self.alphas_to_save is not None and self.rs_path is not None:
  201. if alpha in self.alphas_to_save:
  202. regime = self.alpha_to_regime(alpha)
  203. with open(os.path.join(self.rs_path, regime +
  204. '_rs_alpha{}'.format(alpha) +
  205. net_id), 'wb') as f:
  206. np.save(f, esn._state[self.warmup:])
  207. #warmup kept in SBM_MC_tanh_nnodes50_p10.5_delta_p0.5_min_weight0_bin_directedTrue_wei_directedFalse_3
  208. def save_LEs(self, esn, alpha, net_id):
  209. if self.compute_LE and self.LE_path is not None:
  210. with open(os.path.join(self.LE_path, 'LEs_alpha{}'.format(alpha) +
  211. net_id), 'wb') as f:
  212. np.save(f, esn.LE)
  213. with open(os.path.join(self.LE_path, 'LEs_trajectory_alpha{}'.format(alpha) +
  214. net_id), 'wb') as f:
  215. np.save(f, esn.LE_trajectory)
  216. def save_results(self):
  217. with open(os.path.join(self.perform_path,
  218. 'results_seed{}.npy'.format(self.seed)), 'wb') as f:
  219. pickle.dump(self.results, f)
  220. #scores are averaged or maxed across output modules
  221. #maxed across alpha values
  222. #averaged across seeds
  223. #maxed across network types
  224. #finally, the highest performing parameter is chosen
  225. def best_hyperparameter(self, niter, nseeds, n_net_types,
  226. aggregate='average',
  227. gain_opt=False, ridge_opt=False,
  228. density_opt=False):
  229. input_gain = None
  230. l2_alpha = None
  231. pruning_ratio = None
  232. scores = []
  233. for scores_niter in self.hyperparameter_results:
  234. if scores_niter is None:
  235. scores.append(np.nan)
  236. continue
  237. scores_type = []
  238. for net_type in range(n_net_types):
  239. max_scores = []
  240. for seed in range(nseeds):
  241. #aggregate scores across output modules
  242. if aggregate == 'average':
  243. df_agg = scores_niter[seed][net_type].groupby('alpha').agg({self.score: 'mean'}).reset_index()
  244. elif aggregate == 'max':
  245. df_agg = scores_niter[seed][net_type].groupby('alpha').agg({self.score: 'max'}).reset_index()
  246. else:
  247. raise ValueError("Invalid aggregation method. "\
  248. "Choose from 'average' or 'max'.")
  249. #max score across alpha values
  250. max_scores.append(df_agg[self.score].max())
  251. #mean score across seeds
  252. scores_type.append(np.mean(max_scores))
  253. #max score across network types
  254. scores.append(np.max(scores_type))
  255. #best hyperparameter
  256. if gain_opt:
  257. self.input_gain = self.param_sampler[np.nanargmax(scores)]['input_gain']
  258. input_gain = self.input_gain
  259. if ridge_opt:
  260. self.l2_alpha = self.param_sampler[np.nanargmax(scores)]['l2_alpha']
  261. l2_alpha = self.l2_alpha
  262. if density_opt:
  263. self.pruning_ratio = self.param_sampler[np.nanargmax(scores)]['pruning_ratio']
  264. pruning_ratio = self.pruning_ratio
  265. return input_gain, l2_alpha, pruning_ratio
  266. class RegressionUtils(ABC):
  267. """
  268. Intermediate abstract class for
  269. Regression tasks
  270. Attributes
  271. ----------
  272. seed : int
  273. Random seed for the task
  274. readout : Readout
  275. Readout object for the task
  276. results : dict
  277. Performance results for the task
  278. input_gain : float
  279. Input gain for the reservoir
  280. l2_alpha : float
  281. Ridge regularization parameter
  282. pruning_ratio : float
  283. Ratio of connections to prune
  284. Methods
  285. -------
  286. set_in_out(seed, nnodes, nodes, nmodules, module_mappings, conn)
  287. Set input and output nodes
  288. analysis(sbms, nnodes, nodes, nmodules, module_mappings, seed)
  289. Run the task
  290. hyperparameter_opt(sbms, nnodes, nodes, nmodules, module_mappings,
  291. nseeds, input_gain=None, l2_alpha=None,
  292. pruning_ratio=None)
  293. Run hyperparameter optimization
  294. """
  295. def set_in_out(self, seed, nnodes, nodes,
  296. nmodules, module_mappings, conn):
  297. np.random.seed(seed)
  298. module = np.random.randint(nmodules)
  299. input_nodes = np.array(range(module*nnodes, module*nnodes + nnodes))
  300. if not self.input_amp:
  301. input_nodes = conn.get_nodes(
  302. 'random', nodes_from=input_nodes, seed=seed
  303. )
  304. output_nodes = (nodes < module*nnodes)|(nodes >= module*nnodes+nnodes)
  305. readout_modules = module_mappings[output_nodes]
  306. w_in = np.zeros((1, conn.n_nodes))
  307. w_in[:, input_nodes] = 1
  308. return module, output_nodes, readout_modules, w_in
  309. def analysis(self, sbms, nnodes, nodes,
  310. nmodules, module_mappings, seed):
  311. print('Running seed {}'.format(seed))
  312. self.seed = seed
  313. self.init_data(len(list(sbms.values())[0]))
  314. self.readout = Readout(estimator=Ridge(alpha=self.l2_alpha,
  315. fit_intercept=False))
  316. scores = {}
  317. for level in sbms.keys():
  318. conn = self.init_conn(self.seed, sbms, level)
  319. module, output_nodes, readout_modules, w_in = self.set_in_out(self.seed, nnodes, nodes,
  320. nmodules, module_mappings,
  321. conn)
  322. net_id = self.init_net_id(level, module=module)
  323. df_alpha = self.task_workflow(conn, w_in, output_nodes,
  324. readout_modules, net_id=net_id,
  325. compute_LE=self.compute_LE,
  326. multioutput=self.multioutput)
  327. scores[level] = df_alpha
  328. self.results = scores
  329. self.save_results()
  330. def hyperparameter_opt(self, sbms, nnodes, nodes,
  331. nmodules, module_mappings, nseeds,
  332. input_gain=None,
  333. l2_alpha=None,
  334. pruning_ratio=None):
  335. if input_gain is not None:
  336. self.input_gain = input_gain
  337. if l2_alpha is not None:
  338. self.l2_alpha = l2_alpha
  339. if pruning_ratio is not None:
  340. self.pruning_ratio = pruning_ratio
  341. self.readout = Readout(estimator=Ridge(alpha=self.l2_alpha,
  342. fit_intercept=False))
  343. scores_seed = []
  344. for seed in range(nseeds):
  345. self.seed = seed + 2*len(list(sbms.values())[0])
  346. self.init_data(nseeds)
  347. scores_net_type = []
  348. for net_type in sbms.keys():
  349. conn = self.init_conn(seed, sbms, net_type)
  350. if conn is None:
  351. return None
  352. module, output_nodes, readout_modules, w_in = self.set_in_out(seed, nnodes, nodes,
  353. nmodules, module_mappings,
  354. conn)
  355. df_alpha = self.task_workflow(conn, w_in, output_nodes,
  356. readout_modules)
  357. scores_net_type.append(df_alpha)
  358. scores_seed.append(scores_net_type)
  359. return scores_seed
  360. class ClassifierUtils(ABC):
  361. """
  362. Intermediate abstract class for
  363. Classification tasks
  364. Attributes
  365. ----------
  366. seed : int
  367. Random seed for the task
  368. readout : Readout
  369. Readout object for the task
  370. results : dict
  371. Performance results for the task
  372. input_gain : float
  373. Input gain for the reservoir
  374. l2_alpha : float
  375. Ridge regularization parameter
  376. pruning_ratio : float
  377. Ratio of connections to prune
  378. Methods
  379. -------
  380. sample_weight()
  381. Get sample weights for the task
  382. analysis(sbms, nnodes, nodes, nmodules, module_mappings, seed)
  383. Run the task
  384. hyperparameter_opt(sbms, nnodes, nodes, nmodules, module_mappings,
  385. nseeds, input_gain=None, l2_alpha=None,
  386. pruning_ratio=None)
  387. Run hyperparameter optimization
  388. """
  389. def sample_weight(self):
  390. if self.sample_weight_strat == 'whole':
  391. sample_weight_train = _get_sample_weight(self.y_train, split_set='train',
  392. grace_period=self.grace_period,
  393. seed=self.seed)
  394. sample_weight_test = _get_sample_weight(self.y_test, split_set='test',
  395. grace_period=self.grace_period)
  396. else:
  397. sample_weight_train, sample_weight_test = _get_sample_weight((self.y_train, self.y_test),
  398. grace_period=self.grace_period)
  399. sample_weight = (sample_weight_train, sample_weight_test)
  400. return sample_weight
  401. def analysis(self, sbms, nnodes, nodes,
  402. nmodules, module_mappings, seed):
  403. print('Running seed {}'.format(seed))
  404. self.seed = seed
  405. self.init_data(len(list(sbms.values())[0]))
  406. sample_weight = self.sample_weight()
  407. self.readout = Readout(estimator=RidgeClassifier(alpha=self.l2_alpha,
  408. fit_intercept=False))
  409. scores = {}
  410. for level in sbms.keys():
  411. conn = self.init_conn(self.seed, sbms, level)
  412. module, output_nodes, readout_modules, w_in = self.set_in_out(self.seed, nnodes, nodes,
  413. nmodules, module_mappings,
  414. conn)
  415. net_id = self.init_net_id(level, module=module)
  416. df_alpha = self.task_workflow(conn, w_in, output_nodes,
  417. readout_modules, net_id=net_id,
  418. compute_LE=self.compute_LE,
  419. sample_weight=sample_weight,
  420. multioutput=self.multioutput)
  421. scores[level] = df_alpha
  422. self.results = scores
  423. self.save_results()
  424. def hyperparameter_opt(self, sbms, nnodes, nodes,
  425. nmodules, module_mappings, nseeds,
  426. input_gain=None,
  427. l2_alpha=None,
  428. pruning_ratio=None):
  429. if input_gain is not None:
  430. self.input_gain = input_gain
  431. if l2_alpha is not None:
  432. self.l2_alpha = l2_alpha
  433. if pruning_ratio is not None:
  434. self.pruning_ratio = pruning_ratio
  435. self.readout = Readout(estimator=RidgeClassifier(alpha=self.l2_alpha,
  436. fit_intercept=False))
  437. scores_seed = []
  438. for seed in range(nseeds):
  439. self.seed = seed + 2*len(list(sbms.values())[0])
  440. self.init_data(nseeds)
  441. sample_weight = self.sample_weight()
  442. scores_net_type = []
  443. for net_type in sbms.keys():
  444. conn = self.init_conn(seed, sbms, net_type)
  445. if conn is None:
  446. return None
  447. module, output_nodes, readout_modules, w_in = self.set_in_out(seed, nnodes, nodes,
  448. nmodules, module_mappings,
  449. conn)
  450. df_alpha = self.task_workflow(conn, w_in, output_nodes,
  451. readout_modules,
  452. sample_weight=sample_weight)
  453. scores_net_type.append(df_alpha)
  454. scores_seed.append(scores_net_type)
  455. return scores_seed
  456. class MemoryCapacityMultitasking(ABC):
  457. """
  458. Intermediate abstract class for Memory Capacity and Multitasking tasks
  459. Methods
  460. -------
  461. get_MC_data(seed)
  462. Generate Memory Capacity task data
  463. """
  464. def get_MC_data(self, seed):
  465. x, y = self.task.fetch_data(n_trials=self.duration,
  466. horizon_max=self.horizon_max,
  467. win=self.warmup, seed=seed)
  468. return x, y
  469. class NonlinearTransformationMultitasking(ABC):
  470. """
  471. Intermediate abstract class for Nonlinear Transformation and
  472. Multitasking tasks
  473. Methods
  474. -------
  475. get_NLT_data(ncycles, lag=False, rand=True, seed=0)
  476. Generate Nonlinear Transformation task data
  477. """
  478. def get_NLT_data(self, ncycles, lag=False, rand=True, seed=0):
  479. total_duration = self.total_duration + 1
  480. t = np.arange(total_duration)
  481. phase = 0
  482. #phase randomization
  483. if rand == True:
  484. np.random.seed(seed)
  485. phase = np.random.uniform(0, 2*np.pi)
  486. #angular conversion
  487. rad = 2*np.pi*ncycles*t/total_duration + phase
  488. x = np.sin(rad)[:, np.newaxis]
  489. x = x[1:]
  490. y = signal.square(rad)
  491. y = y[1:]
  492. if lag == True:
  493. y = y[self.warmup - 1: -1]
  494. else:
  495. y = y[self.warmup:]
  496. return x, y
  497. class ChaoticPrediction(Task, RegressionUtils):
  498. """
  499. Class for running the Chaotic Prediction task
  500. Attributes
  501. ----------
  502. horizon : int
  503. Horizon for the task
  504. task_name : str
  505. Name of the task
  506. task : ReservoirPyTask
  507. Task object for the task
  508. input_amp : bool
  509. Flag for amplifying the input signal
  510. distributed_input : bool
  511. Flag for distributing the input signal
  512. min : float
  513. Minimum value for the initial conditions
  514. max : float
  515. Maximum value for the initial conditions
  516. data_kwargs : dict
  517. Additional keyword arguments for the task
  518. training_noise : bool
  519. Flag for adding noise to the training data
  520. noise_factor : float
  521. Noise factor
  522. test_split : int
  523. Test split duration
  524. x_train : np.ndarray
  525. Training input data
  526. y_train : np.ndarray
  527. Training output data
  528. x_test : np.ndarray
  529. Testing input data
  530. y_test : np.ndarray
  531. Testing output data
  532. Methods
  533. -------
  534. init_conds(seed)
  535. Initialize initial conditions for the task
  536. init_data(nseeds)
  537. Initialize Chaotic Prediction task data
  538. set_in_out(seed, nnodes, nodes, nmodules, module_mappings, conn)
  539. Set input and output nodes
  540. """
  541. def __init__(self, config, **kwargs):
  542. super().__init__(config)
  543. self.horizon = config.horizon
  544. self.task_name = config.task_name
  545. self.task = ReservoirPyTask(name=self.task_name)
  546. self.input_amp = config.input_amp
  547. self.distributed_input = config.distributed_input
  548. self.min = config.init_min
  549. self.max = config.init_max
  550. self.data_kwargs = kwargs
  551. self.training_noise = config.training_noise
  552. self.noise_factor = config.noise_factor
  553. self.test_split = config.test_split
  554. def init_conds(self, seed):
  555. np.random.seed(seed)
  556. if self.task_name == 'henon_map':
  557. x0 = np.random.uniform(self.min, self.max, 2)
  558. elif self.task_name == 'logistic_map':
  559. x0 = np.random.uniform(self.min, self.max, 1)
  560. elif self.task_name == 'lorenz':
  561. x0 = np.random.uniform(self.min, self.max, 3)
  562. elif self.task_name == 'mackey_glass':
  563. x0 = np.random.uniform(self.min, self.max, 1)
  564. elif self.task_name == 'multiscroll':
  565. x0 = np.random.uniform(self.min, self.max, 3)
  566. elif self.task_name == 'doublescroll':
  567. x0 = np.random.uniform(self.min, self.max, 3)
  568. elif self.task_name == 'rabinovich_fabrikant':
  569. x0 = np.random.uniform(self.min, self.max, 3)
  570. elif self.task_name == 'narma':
  571. x0 = np.random.uniform(self.min, self.max, 1)
  572. elif self.task_name == 'lorenz96':
  573. size = self.data_kwargs.get('N', 36)
  574. x0 = np.random.uniform(self.min, self.max, size)
  575. elif self.task_name == 'rossler':
  576. x0 = np.random.uniform(self.min, self.max, 3)
  577. return x0
  578. def init_data(self, nseeds):
  579. if 'x0' not in self.data_kwargs:
  580. x0 = self.init_conds(self.seed)
  581. self.data_kwargs['x0'] = x0
  582. if self.task_name == 'mackey_glass' or self.task_name == 'narma':
  583. if 'seed' not in self.data_kwargs:
  584. self.data_kwargs['seed'] = self.seed
  585. custom_u = False
  586. if self.task_name == 'narma':
  587. if 'u_min' in self.data_kwargs or 'u_max' in self.data_kwargs:
  588. custom_u = True
  589. u_min = 0 if 'u_min' not in self.data_kwargs else self.data_kwargs['u_min']
  590. u_max = 0.45 if 'u_max' not in self.data_kwargs else self.data_kwargs['u_max']
  591. order = 30 if 'order' not in self.data_kwargs else self.data_kwargs['order']
  592. #delete u_min and u_max from data_kwargs
  593. if 'u_min' in self.data_kwargs:
  594. del self.data_kwargs['u_min']
  595. if 'u_max' in self.data_kwargs:
  596. del self.data_kwargs['u_max']
  597. np.random.seed(self.seed)
  598. duration = self.duration + self.warmup + np.abs(self.horizon) + 1 + order
  599. u = np.random.uniform(u_min, u_max, duration)
  600. u = u[:, np.newaxis]
  601. self.data_kwargs['u'] = u
  602. self.x_train, self.y_train = self.task.fetch_data(n_trials=self.duration,
  603. horizon=self.horizon,
  604. win=self.warmup,
  605. **self.data_kwargs)
  606. if self.training_noise:
  607. np.random.seed(self.seed)
  608. x_train_mean = np.mean(self.x_train)
  609. self.x_train += np.random.uniform(x_train_mean - self.noise_factor*x_train_mean,
  610. x_train_mean + self.noise_factor*x_train_mean,
  611. self.x_train.shape)
  612. if 'x0' not in self.data_kwargs:
  613. x0 = self.init_conds(self.seed + nseeds)
  614. self.data_kwargs['x0'] = x0
  615. if self.task_name == 'mackey_glass' or self.task_name == 'narma':
  616. if 'seed' not in self.data_kwargs:
  617. self.data_kwargs['seed'] = self.seed + nseeds
  618. if custom_u:
  619. np.random.seed(self.seed + nseeds)
  620. u = np.random.uniform(u_min, u_max, duration)
  621. u = u[:, np.newaxis]
  622. self.data_kwargs['u'] = u
  623. self.x_test, self.y_test = self.task.fetch_data(n_trials=self.duration,
  624. horizon=self.horizon,
  625. win=self.warmup,
  626. **self.data_kwargs)
  627. def set_in_out(self, seed, nnodes, nodes,
  628. nmodules, module_mappings, conn):
  629. np.random.seed(seed)
  630. w_in = np.zeros((self.task.n_features, conn.n_nodes))
  631. #make sure there are not more task features than modules
  632. if (self.task.n_features == 1 or
  633. (self.distributed_input and self.task.n_features < nmodules)):
  634. #select the input modules
  635. input_modules = np.random.choice(range(nmodules), self.task.n_features, replace=False)
  636. #all other modules are output modules
  637. output_modules = np.array([i for i in range(nmodules) if i not in input_modules])
  638. input_nodes = []
  639. for input_module in input_modules:
  640. potential_input_nodes = np.array(range(input_module*nnodes,
  641. input_module*nnodes +
  642. nnodes))
  643. if self.input_amp:
  644. input_nodes.append(potential_input_nodes)
  645. #select a random node in each input module if not amplifying
  646. else:
  647. input_node = conn.get_nodes(
  648. 'random', nodes_from=potential_input_nodes, seed=seed
  649. )
  650. input_nodes.append(input_node)
  651. output_nodes = []
  652. readout_modules = []
  653. for output_module in output_modules:
  654. curr_output_nodes = np.array(range(output_module*nnodes,
  655. output_module*nnodes +
  656. nnodes))
  657. output_nodes.append(curr_output_nodes)
  658. readout_modules.append(module_mappings[curr_output_nodes])
  659. output_nodes = np.concatenate(output_nodes)
  660. #map the input signals to the input nodes
  661. for i in range(self.task.n_features):
  662. w_in[i, input_nodes[i]] = 1
  663. if self.task.n_features == 1:
  664. module = input_modules[0]
  665. else:
  666. module = None
  667. input_nodes = np.concatenate(input_nodes)
  668. output_nodes = np.array([i for i in range(conn.n_nodes) if i not in input_nodes])
  669. readout_modules = [0]*len(output_nodes)
  670. else:
  671. #warn that distributed input is not possible
  672. if self.distributed_input:
  673. print("Distributed input is not possible for this task.")
  674. #select a random module
  675. module = np.random.randint(nmodules)
  676. #select the input nodes from the module
  677. potential_input_nodes = np.array(range(module*nnodes,
  678. module*nnodes +
  679. nnodes))
  680. input_nodes = conn.get_nodes(
  681. 'random', nodes_from=potential_input_nodes,
  682. n_nodes=self.task.n_features, seed=seed
  683. )
  684. #all other modules are output modules
  685. output_nodes = np.array([i for i in range(conn.n_nodes) if i not in input_nodes])
  686. readout_modules = [0]*len(output_nodes)
  687. #map the input signals to the input nodes
  688. w_in[:, input_nodes] = np.eye(self.task.n_features)
  689. return module, output_nodes, readout_modules, w_in
  690. class NeurogymTask(Task, ClassifierUtils):
  691. """
  692. Class for running Neurogym tasks
  693. Attributes
  694. ----------
  695. task_name : str
  696. Name of the task
  697. task : NeurogymTask
  698. Task object for the task
  699. input_amp : bool
  700. Flag for amplifying the input signal
  701. distributed_input : bool
  702. Flag for distributing the input signal
  703. sample_weight_strat : str
  704. Sample weight strategy
  705. grace_period : int
  706. Grace period before evaluating
  707. training_noise : bool
  708. Flag for adding noise to the training data
  709. testing_noise : bool
  710. Flag for adding noise to the testing data
  711. max_noise : float
  712. Maximum noise amplitude
  713. data_kwargs : dict
  714. Additional keyword arguments for the task
  715. save_io_data_path : str
  716. Path to save input and output data
  717. load_io_data_path : str
  718. Path to load input and output data
  719. x_train : np.ndarray
  720. Training input data
  721. y_train : np.ndarray
  722. Training output data
  723. x_test : np.ndarray
  724. Testing input data
  725. y_test : np.ndarray
  726. Testing output data
  727. Methods
  728. -------
  729. init_data(nseeds)
  730. Initialize Neurogym task data
  731. save_io_data(nseeds)
  732. Save input and output data
  733. set_in_out(seed, nnodes, nodes, nmodules, module_mappings, conn)
  734. Set input and output nodes
  735. """
  736. def __init__(self, config, **kwargs):
  737. super().__init__(config)
  738. self.task_name = config.task_name
  739. self.task = NeuroGymTask(name=self.task_name)
  740. self.input_amp = config.input_amp
  741. self.distributed_input = config.distributed_input
  742. self.sample_weight_strat = config.sample_weight_strat
  743. self.grace_period = config.grace_period
  744. self.training_noise = config.training_noise
  745. self.testing_noise = config.testing_noise
  746. self.max_noise = config.max_noise
  747. self.data_kwargs = kwargs
  748. self.save_io_data_path = config.save_io_data_path
  749. self.load_io_data_path = config.load_io_data_path
  750. def init_data(self, nseeds):
  751. if self.load_io_data_path is not None:
  752. with open(os.path.join(self.load_io_data_path,
  753. 'x_train_seed{}.pickle'.format(self.seed)), 'rb') as f:
  754. self.x_train = pickle.load(f)
  755. with open(os.path.join(self.load_io_data_path,
  756. 'y_train_seed{}.pickle'.format(self.seed)), 'rb') as f:
  757. self.y_train = pickle.load(f)
  758. with open(os.path.join(self.load_io_data_path,
  759. 'x_test_seed{}.pickle'.format(self.seed + nseeds)), 'rb') as f:
  760. self.x_test = pickle.load(f)
  761. with open(os.path.join(self.load_io_data_path,
  762. 'y_test_seed{}.pickle'.format(self.seed + nseeds)), 'rb') as f:
  763. self.y_test = pickle.load(f)
  764. #was already saved for this experiment
  765. elif os.path.exists(os.path.join(self.save_io_data_path, 'x_train_seed{}.pickle'.format(self.seed))):
  766. with open(os.path.join(self.save_io_data_path,
  767. 'x_train_seed{}.pickle'.format(self.seed)), 'rb') as f:
  768. self.x_train = pickle.load(f)
  769. with open(os.path.join(self.save_io_data_path,
  770. 'y_train_seed{}.pickle'.format(self.seed)), 'rb') as f:
  771. self.y_train = pickle.load(f)
  772. with open(os.path.join(self.save_io_data_path,
  773. 'x_test_seed{}.pickle'.format(self.seed + nseeds)), 'rb') as f:
  774. self.x_test = pickle.load(f)
  775. with open(os.path.join(self.save_io_data_path,
  776. 'y_test_seed{}.pickle'.format(self.seed + nseeds)), 'rb') as f:
  777. self.y_test = pickle.load(f)
  778. else:
  779. self.x_train, self.y_train = self.task.fetch_data(n_trials=self.duration, **self.data_kwargs)
  780. if self.training_noise:
  781. np.random.seed(self.seed)
  782. for trial in range(len(self.x_train)):
  783. self.x_train[trial] += np.random.uniform(-self.max_noise, self.max_noise, self.x_train[trial].shape)
  784. self.x_test, self.y_test = self.task.fetch_data(n_trials=self.duration, **self.data_kwargs)
  785. if self.testing_noise:
  786. np.random.seed(self.seed + nseeds)
  787. for trial in range(len(self.x_test)):
  788. self.x_test[trial] += np.random.uniform(-self.max_noise, self.max_noise, self.x_test[trial].shape)
  789. self.save_io_data(nseeds)
  790. def save_io_data(self, nseeds):
  791. with open(os.path.join(self.save_io_data_path,
  792. 'x_train_seed{}.pickle'.format(self.seed)), 'wb') as f:
  793. pickle.dump(self.x_train, f)
  794. with open(os.path.join(self.save_io_data_path,
  795. 'y_train_seed{}.pickle'.format(self.seed)), 'wb') as f:
  796. pickle.dump(self.y_train, f)
  797. with open(os.path.join(self.save_io_data_path,
  798. 'x_test_seed{}.pickle'.format(self.seed + nseeds)), 'wb') as f:
  799. pickle.dump(self.x_test, f)
  800. with open(os.path.join(self.save_io_data_path,
  801. 'y_test_seed{}.pickle'.format(self.seed + nseeds)), 'wb') as f:
  802. pickle.dump(self.y_test, f)
  803. def set_in_out(self, seed, nnodes, nodes,
  804. nmodules, module_mappings, conn):
  805. np.random.seed(seed)
  806. w_in = np.zeros((self.task.n_features, conn.n_nodes))
  807. #make sure there are not more task features than modules
  808. if (self.task.n_features == 1 or
  809. (self.distributed_input and self.task.n_features < nmodules)):
  810. #select the input modules
  811. input_modules = np.random.choice(range(nmodules), self.task.n_features, replace=False)
  812. #all other modules are output modules
  813. output_modules = np.array([i for i in range(nmodules) if i not in input_modules])
  814. input_nodes = []
  815. for input_module in input_modules:
  816. potential_input_nodes = np.array(range(input_module*nnodes,
  817. input_module*nnodes +
  818. nnodes))
  819. if self.input_amp:
  820. input_nodes.append(potential_input_nodes)
  821. #select a random node in each input module if not amplifying
  822. else:
  823. input_node = conn.get_nodes(
  824. 'random', nodes_from=potential_input_nodes, seed=seed
  825. )
  826. input_nodes.append(input_node)
  827. output_nodes = []
  828. readout_modules = []
  829. for output_module in output_modules:
  830. curr_output_nodes = np.array(range(output_module*nnodes,
  831. output_module*nnodes +
  832. nnodes))
  833. output_nodes.append(curr_output_nodes)
  834. readout_modules.append(module_mappings[curr_output_nodes])
  835. output_nodes = np.concatenate(output_nodes)
  836. #map the input signals to the input nodes
  837. for i in range(self.task.n_features):
  838. w_in[i, input_nodes[i]] = 1
  839. module = None
  840. else:
  841. #warn that distributed input is not possible
  842. if self.distributed_input:
  843. print("Distributed input is not possible for this task.")
  844. #select a random module
  845. module = np.random.randint(nmodules)
  846. #select the input nodes from the module
  847. potential_input_nodes = np.array(range(module*nnodes,
  848. module*nnodes +
  849. nnodes))
  850. input_nodes = conn.get_nodes(
  851. 'random', nodes_from=potential_input_nodes,
  852. n_nodes=self.task.n_features, seed=seed
  853. )
  854. #all other modules are output modules
  855. output_nodes = (nodes < module*nnodes)|(nodes >= module*nnodes+nnodes)
  856. readout_modules = module_mappings[output_nodes]
  857. #map the input signals to the input nodes
  858. w_in[:, input_nodes] = np.eye(self.task.n_features)
  859. return module, output_nodes, readout_modules, w_in
  860. class MemoryCapacity(Task, RegressionUtils, MemoryCapacityMultitasking):
  861. """
  862. Class for running the Memory Capacity task
  863. Attributes
  864. ----------
  865. horizon_max : int
  866. Maximum time-lag for the task
  867. task : Conn2ResTask
  868. Task object for the task
  869. input_amp : bool
  870. Flag for amplifying the input signal
  871. training_noise : bool
  872. Flag for adding noise to the training data
  873. testing_noise : bool
  874. Flag for adding noise to the testing data
  875. max_noise : float
  876. Maximum noise amplitude
  877. x_train : np.ndarray
  878. Training input data
  879. y_train : np.ndarray
  880. Training output data
  881. x_test : np.ndarray
  882. Testing input data
  883. y_test : np.ndarray
  884. Testing output data
  885. Methods
  886. -------
  887. init_data(nseeds)
  888. Initialize Memory Capacity task data
  889. """
  890. def __init__(self, config):
  891. super().__init__(config)
  892. self.horizon_max = config.horizon_max
  893. self.task = Conn2ResTask(name='MemoryCapacity')
  894. self.input_amp = config.input_amp
  895. self.training_noise = config.training_noise
  896. self.testing_noise = config.testing_noise
  897. self.max_noise = config.max_noise
  898. def init_data(self, nseeds):
  899. self.x_train, self.y_train = self.get_MC_data(self.seed)
  900. if self.training_noise:
  901. np.random.seed(self.seed)
  902. self.x_train += np.random.uniform(-self.max_noise, self.max_noise,
  903. self.x_train.shape)
  904. self.x_test, self.y_test = self.get_MC_data(self.seed + nseeds)
  905. if self.testing_noise:
  906. np.random.seed(self.seed + nseeds)
  907. self.x_test += np.random.uniform(-self.max_noise, self.max_noise,
  908. self.x_test.shape)
  909. class EmpiricalMC(MemoryCapacity):
  910. """
  911. Class for running the Memory Capacity task
  912. on empirical connectivity matrices
  913. Attributes
  914. ----------
  915. seed : int
  916. Random seed for the task
  917. readout : Readout
  918. Readout object for the task
  919. results : dict
  920. Performance results for the task
  921. input_gain : float
  922. Input gain for the reservoir
  923. l2_alpha : float
  924. Ridge regularization parameter
  925. Methods
  926. -------
  927. set_in_out(seed, conn, module, module_mappings, noutputs)
  928. Set input and output nodes for empirical connectivity matrices
  929. analysis(nets, module_mappings, noutputs, seed)
  930. Run the Memory Capacity task on empirical connectivity matrices
  931. hyperparameter_opt(nets, module_mappings, noutputs,
  932. niter, nseeds, sampler_seed,
  933. gain_extrema=None, ridge_extrema=None,
  934. pruning_extrema=None)
  935. Run hyperparameter optimization for the Memory Capacity task
  936. on empirical connectivity matrices
  937. best_hyperparameter(niter, nseeds, aggregate='average',
  938. gain_opt=False, ridge_opt=False,
  939. density_opt=False)
  940. Return the best empirical hyperparameters
  941. """
  942. def __init__(self, config):
  943. super().__init__(config)
  944. def set_in_out(self, seed, conn,
  945. module, module_mappings, noutputs):
  946. input_nodes = np.where(module_mappings == module)[0]
  947. potential_output_nodes = np.where(module_mappings != module)[0]
  948. output_modules = np.unique(module_mappings[potential_output_nodes])
  949. output_nodes = []
  950. #picking the same number of output nodes for each output module
  951. #to ensure a similar dimensionality expansion
  952. for output_module in output_modules:
  953. curr_output_nodes = conn.get_nodes('random', nodes_from=np.where(module_mappings == output_module)[0],
  954. n_nodes=noutputs, seed=seed)
  955. output_nodes.append(curr_output_nodes)
  956. output_nodes = np.concatenate(output_nodes)
  957. readout_modules = module_mappings[output_nodes]
  958. w_in = np.zeros((1, conn.n_nodes))
  959. w_in[:, input_nodes] = 1
  960. return output_nodes, readout_modules, w_in
  961. def analysis(self, nets, module_mappings, noutputs, seed):
  962. print('Running seed {}'.format(seed))
  963. self.seed = seed
  964. self.init_data(len(list(nets.values())[-1]))
  965. self.readout = Readout(estimator=Ridge(alpha=self.l2_alpha,
  966. fit_intercept=False))
  967. MCs = {}
  968. for level in nets.keys():
  969. if level == 'empirical' and self.seed > 0:
  970. conn = self.init_conn(0, nets, level)
  971. else:
  972. conn = self.init_conn(self.seed, nets, level)
  973. MC_modules = []
  974. #have to all be looped because of size and connectivity variability
  975. for module in np.unique(module_mappings):
  976. output_nodes, readout_modules, w_in = self.set_in_out(self.seed, conn,
  977. module, module_mappings,
  978. noutputs)
  979. net_id = self.init_net_id(level, module=module)
  980. df_alpha = self.task_workflow(conn, w_in, output_nodes,
  981. readout_modules, net_id=net_id,
  982. compute_LE=self.compute_LE,
  983. multioutput=self.multioutput)
  984. MC_modules.append(df_alpha)
  985. MCs[level] = MC_modules
  986. self.results = MCs
  987. self.save_results()
  988. def hyperparameter_opt(self, nets, module_mappings,
  989. noutputs, nseeds,
  990. input_gain=None,
  991. l2_alpha=None,
  992. pruning_ratio=None):
  993. if input_gain is not None:
  994. self.input_gain = input_gain
  995. if l2_alpha is not None:
  996. self.l2_alpha = l2_alpha
  997. self.readout = Readout(estimator=Ridge(alpha=self.l2_alpha,
  998. fit_intercept=False))
  999. conn = self.init_conn(0, nets, 'empirical')
  1000. MC_seed = []
  1001. for seed in range(nseeds):
  1002. self.seed = seed + 2*len(list(nets.values())[-1])
  1003. self.init_data(nseeds)
  1004. MC_modules = []
  1005. for module in np.unique(module_mappings):
  1006. output_nodes, readout_modules, w_in = self.set_in_out(seed, conn,
  1007. module, module_mappings,
  1008. noutputs)
  1009. df_alpha = self.task_workflow(conn, w_in, output_nodes,
  1010. readout_modules)
  1011. MC_modules.append(df_alpha)
  1012. MC_seed.append(MC_modules)
  1013. return MC_seed
  1014. #scores are averaged or maxed across output and input modules
  1015. #maxed across alpha values
  1016. #averaged across seeds
  1017. #finally, the highest performing parameter is chosen
  1018. def best_hyperparameter(self, niter, nseeds, n_net_types,
  1019. aggregate='average',
  1020. gain_opt=False, ridge_opt=False,
  1021. density_opt=False):
  1022. input_gain = None
  1023. l2_alpha = None
  1024. pruning_ratio = None
  1025. scores = []
  1026. for MC_niter in self.hyperparameter_results:
  1027. agg_scores = []
  1028. for MC_seed in MC_niter:
  1029. max_scores = []
  1030. for MC in MC_seed:
  1031. #aggregate scores across output modules
  1032. if aggregate == 'average':
  1033. df_agg = MC.groupby('alpha').agg({self.score: 'mean'}).reset_index()
  1034. elif aggregate == 'max':
  1035. df_agg = MC.groupby('alpha').agg({self.score: 'max'}).reset_index()
  1036. else:
  1037. raise ValueError("Invalid aggregation method. "\
  1038. "Choose from 'average' or 'max'.")
  1039. #max score across alpha values
  1040. max_scores.append(df_agg[self.score].max())
  1041. #aggregate scores across input modules
  1042. if aggregate == 'average':
  1043. agg_scores.append(np.mean(max_scores))
  1044. elif aggregate == 'max':
  1045. agg_scores.append(np.max(max_scores))
  1046. else:
  1047. raise ValueError("Invalid aggregation method. "\
  1048. "Choose from 'average' or 'max'.")
  1049. #mean score across seeds
  1050. scores.append(np.mean(agg_scores))
  1051. #best hyperparameter
  1052. if gain_opt:
  1053. self.input_gain = self.param_sampler[np.argmax(scores)]['input_gain']
  1054. input_gain = self.input_gain
  1055. if ridge_opt:
  1056. self.l2_alpha = self.param_sampler[np.argmax(scores)]['l2_alpha']
  1057. l2_alpha = self.l2_alpha
  1058. return input_gain, l2_alpha, pruning_ratio
  1059. class NLT(Task, RegressionUtils, NonlinearTransformationMultitasking):
  1060. """
  1061. Class for running the Nonlinear Transformation task
  1062. Attributes
  1063. ----------
  1064. ncycles : int
  1065. Number of cycles for the task
  1066. lag : bool
  1067. Whether to use the lagged version of the task
  1068. input_amp : bool
  1069. Flag for amplifying the input signal
  1070. x_train : np.ndarray
  1071. Training input data
  1072. y_train : np.ndarray
  1073. Training output data
  1074. x_test : np.ndarray
  1075. Testing input data
  1076. y_test : np.ndarray
  1077. Testing output data
  1078. Methods
  1079. -------
  1080. init_NLT_data(nseeds)
  1081. Initialize Nonlinear Transformation task data
  1082. """
  1083. def __init__(self, config):
  1084. super().__init__(config)
  1085. self.ncycles = config.ncycles
  1086. self.lag = config.lag
  1087. self.input_amp = config.input_amp
  1088. def init_data(self, nseeds):
  1089. self.x_train, self.y_train = self.get_NLT_data(self.ncycles, lag=self.lag, seed=self.seed)
  1090. self.x_test, self.y_test = self.get_NLT_data(self.ncycles, lag=self.lag, seed=self.seed + nseeds)
  1091. class Multitasking(Task, MemoryCapacityMultitasking, NonlinearTransformationMultitasking):
  1092. """
  1093. Class for running the Multitasking task
  1094. Attributes
  1095. ----------
  1096. ninputs : int
  1097. Number of input signals
  1098. interleaved : bool
  1099. Whether the tasks are interleaved
  1100. ncycles1 : int
  1101. Number of cycles for the first task
  1102. ncycles2 : int
  1103. Number of cycles for the second task
  1104. ncycles3 : int
  1105. Number of cycles for the third task
  1106. ncycles4 : int
  1107. Number of cycles for the fourth task
  1108. lag : bool
  1109. Whether to use the lagged version of the Nonlinear Transformation task
  1110. horizon_max : int
  1111. Maximum time-lag for the Memory Capacity task
  1112. task : Conn2ResTask
  1113. Task object for the Memory Capacity task
  1114. training_noise : bool
  1115. Flag for adding noise to the training data
  1116. testing_noise : bool
  1117. Flag for adding noise to the testing data
  1118. max_noise : float
  1119. Maximum noise amplitude
  1120. x_train : np.ndarray
  1121. Training input data
  1122. y_train : np.ndarray
  1123. Training output data
  1124. x_test : np.ndarray
  1125. Testing input data
  1126. y_test : np.ndarray
  1127. Testing output data
  1128. seed : int
  1129. Random seed for the task
  1130. readout : Readout
  1131. Readout object for the task
  1132. results : dict
  1133. Performance results for the task
  1134. input_gain : float
  1135. Input gain for the reservoir
  1136. l2_alpha : float
  1137. Ridge regularization parameter
  1138. pruning_ratio : float
  1139. Ratio of connections to prune
  1140. Methods
  1141. -------
  1142. init_multitasking_data(nseeds)
  1143. Initialize Multitasking task data
  1144. set_in_out(seed, conn, nnodes, nmodules, module_mappings)
  1145. Set input and output nodes for the Multitasking task
  1146. task_workflow(conn, w_in, nnodes, output_nodes, nmodules, readout_modules,
  1147. net_id=None, compute_LE=False, sample_weight=None,
  1148. multioutput='uniform_average')
  1149. Run the Multitasking task
  1150. analysis(sbms, nnodes, nodes, nmodules, module_mappings, seed)
  1151. Run a Multitasking analysis
  1152. hyperparameter_opt(sbms, nnodes, nodes, nmodules, module_mappings,
  1153. nseeds, input_gain=None, l2_alpha=None,
  1154. pruning_ratio=None)
  1155. Run hyperparameter optimization for the Multitasking task
  1156. best_hyperparameter(niter, nseeds,
  1157. gain_opt=False, ridge_opt=False,
  1158. density_opt=False)
  1159. Return the best multitasking hyperparameters
  1160. """
  1161. def __init__(self, config):
  1162. super().__init__(config)
  1163. self.ninputs = config.ninputs
  1164. self.interleaved = config.interleaved
  1165. self.ncycles1 = config.ncycles[0]
  1166. if self.ninputs == 4:
  1167. self.ncycles2 = config.ncycles[1]
  1168. elif self.ninputs == 8:
  1169. self.ncycles2 = config.ncycles[1]
  1170. self.ncycles3 = config.ncycles[2]
  1171. self.ncycles4 = config.ncycles[3]
  1172. self.lag = config.lag
  1173. self.horizon_max = config.horizon_max
  1174. self.task = Conn2ResTask(name='MemoryCapacity')
  1175. self.training_noise = config.training_noise
  1176. self.testing_noise = config.testing_noise
  1177. self.max_noise = config.max_noise
  1178. def init_multitasking_data(self, nseeds):
  1179. x1_train, y1_train = self.get_MC_data(self.seed)
  1180. x1_test, y1_test = self.get_MC_data(self.seed + nseeds)
  1181. if self.ninputs == 4:
  1182. x2_train, y2_train = self.get_MC_data(self.seed + 2*nseeds)
  1183. x2_test, y2_test = self.get_MC_data(self.seed + 3*nseeds)
  1184. elif self.ninputs == 8:
  1185. x2_train, y2_train = self.get_MC_data(self.seed + 2*nseeds)
  1186. x2_test, y2_test = self.get_MC_data(self.seed + 3*nseeds)
  1187. x3_train, y3_train = self.get_MC_data(self.seed + 4*nseeds)
  1188. x3_test, y3_test = self.get_MC_data(self.seed + 5*nseeds)
  1189. x4_train, y4_train = self.get_MC_data(self.seed + 6*nseeds)
  1190. x4_test, y4_test = self.get_MC_data(self.seed + 7*nseeds)
  1191. x5_train, y5_train = self.get_NLT_data(self.ncycles1, lag=self.lag, seed=self.seed)
  1192. x5_test, y5_test = self.get_NLT_data(self.ncycles1, lag=self.lag, seed=self.seed + nseeds)
  1193. if self.ninputs == 4:
  1194. x6_train, y6_train = self.get_NLT_data(self.ncycles2, lag=self.lag, seed=self.seed + 2*nseeds)
  1195. x6_test, y6_test = self.get_NLT_data(self.ncycles2, lag=self.lag, seed=self.seed + 3*nseeds)
  1196. elif self.ninputs == 8:
  1197. x6_train, y6_train = self.get_NLT_data(self.ncycles2, lag=self.lag, seed=self.seed + 2*nseeds)
  1198. x6_test, y6_test = self.get_NLT_data(self.ncycles2, lag=self.lag, seed=self.seed + 3*nseeds)
  1199. x7_train, y7_train = self.get_NLT_data(self.ncycles3, lag=self.lag, seed=self.seed + 4*nseeds)
  1200. x7_test, y7_test = self.get_NLT_data(self.ncycles3, lag=self.lag, seed=self.seed + 5*nseeds)
  1201. x8_train, y8_train = self.get_NLT_data(self.ncycles4, lag=self.lag, seed=self.seed + 6*nseeds)
  1202. x8_test, y8_test = self.get_NLT_data(self.ncycles4, lag=self.lag, seed=self.seed + 7*nseeds)
  1203. if self.ninputs == 2:
  1204. self.x_train = np.hstack((x1_train, x5_train))
  1205. self.x_test = np.hstack((x1_test, x5_test))
  1206. self.y_train = [y1_train, y5_train]
  1207. self.y_test = [y1_test, y5_test]
  1208. elif self.ninputs == 4:
  1209. if self.interleaved:
  1210. self.x_train = np.hstack((x1_train, x5_train, x2_train, x6_train))
  1211. self.x_test = np.hstack((x1_test, x5_test, x2_test, x6_test))
  1212. self.y_train = [y1_train, y5_train, y2_train, y6_train]
  1213. self.y_test = [y1_test, y5_test, y2_test, y6_test]
  1214. else:
  1215. self.x_train = np.hstack((x1_train, x2_train, x5_train, x6_train))
  1216. self.x_test = np.hstack((x1_test, x2_test, x5_test, x6_test))
  1217. self.y_train = [y1_train, y2_train, y5_train, y6_train]
  1218. self.y_test = [y1_test, y2_test, y5_test, y6_test]
  1219. elif self.ninputs == 8:
  1220. if self.interleaved:
  1221. self.x_train = np.hstack((x1_train, x5_train, x2_train, x6_train,
  1222. x3_train, x7_train, x4_train, x8_train))
  1223. self.x_test = np.hstack((x1_test, x5_test, x2_test, x6_test,
  1224. x3_test, x7_test, x4_test, x8_test))
  1225. self.y_train = [y1_train, y5_train, y2_train, y6_train,
  1226. y3_train, y7_train, y4_train, y8_train]
  1227. self.y_test = [y1_test, y5_test, y2_test, y6_test,
  1228. y3_test, y7_test, y4_test, y8_test]
  1229. else:
  1230. self.x_train = np.hstack((x1_train, x2_train, x3_train, x4_train,
  1231. x5_train, x6_train, x7_train, x8_train))
  1232. self.x_test = np.hstack((x1_test, x2_test, x3_test, x4_test,
  1233. x5_test, x6_test, x7_test, x8_test))
  1234. self.y_train = [y1_train, y2_train, y3_train, y4_train,
  1235. y5_train, y6_train, y7_train, y8_train]
  1236. self.y_test = [y1_test, y2_test, y3_test, y4_test,
  1237. y5_test, y6_test, y7_test, y8_test]
  1238. if self.training_noise:
  1239. np.random.seed(self.seed)
  1240. for trial in range(len(self.x_train)):
  1241. self.x_train[trial] += np.random.uniform(-self.max_noise, self.max_noise, self.x_train[trial].shape)
  1242. if self.testing_noise:
  1243. np.random.seed(self.seed + nseeds)
  1244. for trial in range(len(self.x_test)):
  1245. self.x_test[trial] += np.random.uniform(-self.max_noise, self.max_noise, self.x_test[trial].shape)
  1246. def set_in_out(self, seed, conn, nnodes, nmodules, module_mappings):
  1247. #one single input node per module
  1248. #all other nodes are output nodes
  1249. if self.ninputs == 8:
  1250. input_nodes = []
  1251. output_nodes = []
  1252. readout_modules = []
  1253. for input in range(self.ninputs):
  1254. module_nodes = np.array(range(input*nnodes,
  1255. input*nnodes + nnodes))
  1256. curr_input_node = conn.get_nodes('random',
  1257. nodes_from=module_nodes,
  1258. seed=seed)
  1259. input_nodes.append(curr_input_node)
  1260. curr_output_nodes = module_nodes[np.where(module_nodes !=
  1261. curr_input_node)]
  1262. output_nodes.append(curr_output_nodes)
  1263. readout_modules.append(module_mappings[curr_output_nodes])
  1264. output_nodes = np.concatenate(output_nodes)
  1265. #two modules in different higher-order modules as input
  1266. #all other modules are output modules
  1267. elif self.ninputs == 2:
  1268. np.random.seed(seed)
  1269. seed1 = np.random.randint(0, nmodules//2)
  1270. seed2 = np.random.randint(nmodules//2, nmodules)
  1271. input_nodes_1 = np.array(range(seed1*nnodes,
  1272. seed1*nnodes + nnodes))
  1273. input_nodes_2 = np.array(range(seed2*nnodes,
  1274. seed2*nnodes + nnodes))
  1275. input_nodes = [input_nodes_1, input_nodes_2]
  1276. output_nodes = []
  1277. readout_modules = []
  1278. for module in range(nmodules):
  1279. if module != seed1 and module != seed2:
  1280. curr_output_nodes = np.array(range(module*nnodes,
  1281. module*nnodes + nnodes))
  1282. output_nodes.append(curr_output_nodes)
  1283. readout_modules.append(module_mappings[curr_output_nodes])
  1284. output_nodes = np.concatenate(output_nodes)
  1285. #input modules are even-numbered
  1286. #output modules are odd-numbered
  1287. elif self.ninputs == 4:
  1288. input_nodes = []
  1289. output_nodes = []
  1290. readout_modules = []
  1291. for input in range(self.ninputs*2):
  1292. module_nodes = np.array(range(input*nnodes,
  1293. input*nnodes + nnodes))
  1294. if input % 2 == 0:
  1295. input_nodes.append(module_nodes)
  1296. else:
  1297. output_nodes.append(module_nodes)
  1298. readout_modules.append(module_mappings[module_nodes])
  1299. output_nodes = np.concatenate(output_nodes)
  1300. w_in = np.zeros((self.ninputs, conn.n_nodes))
  1301. for i in range(self.ninputs):
  1302. w_in[i, input_nodes[i]] = 1
  1303. return output_nodes, readout_modules, w_in
  1304. def task_workflow(self, conn, w_in, nnodes, output_nodes,
  1305. nmodules, readout_modules, net_id=None,
  1306. compute_LE=False, multioutput='uniform_average'):
  1307. if self.ninputs == 8:
  1308. nnodes -= 1
  1309. noutput_modules = range(nmodules)
  1310. elif self.ninputs == 2:
  1311. noutput_modules = range(nmodules - self.ninputs)
  1312. elif self.ninputs == 4:
  1313. noutput_modules = range(nmodules - self.ninputs)
  1314. df_alpha = []
  1315. for alpha in self.alphas:
  1316. esn, rs_train, rs_test = self.simulation(alpha, conn, w_in,
  1317. output_nodes, compute_LE,
  1318. multioutput, 'MT')
  1319. if net_id is not None:
  1320. self.save_rs(esn, alpha, net_id)
  1321. if compute_LE:
  1322. self.save_LEs(esn, alpha, net_id)
  1323. for module in noutput_modules:
  1324. curr_rs_train = rs_train[:, module*nnodes:(module + 1)*nnodes]
  1325. curr_rs_test = rs_test[:, module*nnodes:(module + 1)*nnodes]
  1326. if self.ninputs == 8 or self.ninputs == 4:
  1327. curr_y_train, curr_y_test = self.y_train[module], self.y_test[module]
  1328. elif self.ninputs == 2:
  1329. y_id = 0 if module < len(noutput_modules)//2 else 1
  1330. curr_y_train, curr_y_test = self.y_train[y_id], self.y_test[y_id]
  1331. df_res = self.readout.run_task(
  1332. X=(curr_rs_train, curr_rs_test),
  1333. y=(curr_y_train, curr_y_test),
  1334. metric=self.score,
  1335. readout_modules=readout_modules[module],
  1336. multioutput=multioutput
  1337. )
  1338. df_res['alpha'] = alpha
  1339. df_alpha.append(df_res)
  1340. full_df_alpha = pd.concat(df_alpha, ignore_index=True)
  1341. avg_df_alpha = full_df_alpha.groupby('alpha')[self.score].mean()
  1342. return full_df_alpha, avg_df_alpha
  1343. def analysis(self, sbms, nnodes, nodes,
  1344. nmodules, module_mappings,
  1345. seed):
  1346. print('Running seed {}'.format(seed))
  1347. self.seed = seed
  1348. self.init_multitasking_data(len(list(sbms.values())[0]))
  1349. self.readout = Readout(estimator=Ridge(alpha=self.l2_alpha,
  1350. fit_intercept=False))
  1351. full_scores = {}
  1352. avg_scores = {}
  1353. for level in sbms.keys():
  1354. conn = self.init_conn(self.seed, sbms, level)
  1355. output_nodes, readout_modules, w_in = self.set_in_out(seed, conn, nnodes,
  1356. nmodules, module_mappings)
  1357. net_id = self.init_net_id(level)
  1358. full_df_alpha, avg_df_alpha = self.task_workflow(conn, w_in, nnodes, output_nodes,
  1359. nmodules, readout_modules,
  1360. net_id=net_id, compute_LE=self.compute_LE,
  1361. multioutput=self.multioutput)
  1362. full_scores[level] = full_df_alpha
  1363. avg_scores[level] = avg_df_alpha
  1364. self.results = (full_scores, avg_scores)
  1365. self.save_results()
  1366. def hyperparameter_opt(self, sbms, nnodes, nodes,
  1367. nmodules, module_mappings,
  1368. nseeds, input_gain=None,
  1369. l2_alpha=None,
  1370. pruning_ratio=None):
  1371. if input_gain is not None:
  1372. self.input_gain = input_gain
  1373. if l2_alpha is not None:
  1374. self.l2_alpha = l2_alpha
  1375. if pruning_ratio is not None:
  1376. self.pruning_ratio = pruning_ratio
  1377. self.readout = Readout(estimator=Ridge(alpha=self.l2_alpha,
  1378. fit_intercept=False))
  1379. scores_seed = []
  1380. for seed in range(nseeds):
  1381. self.seed = seed + self.ninputs*len(list(sbms.values())[0])
  1382. self.init_multitasking_data(nseeds)
  1383. scores_net_type = []
  1384. for net_type in sbms.keys():
  1385. conn = self.init_conn(seed, sbms, net_type)
  1386. if conn is None:
  1387. return None
  1388. output_nodes, readout_modules, w_in = self.set_in_out(seed, conn, nnodes,
  1389. nmodules, module_mappings)
  1390. full_df_alpha, avg_df_alpha = self.task_workflow(conn, w_in, nnodes, output_nodes,
  1391. nmodules, readout_modules)
  1392. scores_net_type.append(avg_df_alpha)
  1393. scores_seed.append(scores_net_type)
  1394. return scores_seed
  1395. #scores are maxed across alpha values
  1396. #averaged across seeds
  1397. #maxed across network types
  1398. #finally, the highest performing parameter is chosen
  1399. def best_hyperparameter(self, niter, nseeds, n_net_types,
  1400. aggregate='average',
  1401. gain_opt=False, ridge_opt=False,
  1402. density_opt=False):
  1403. if aggregate != 'average':
  1404. raise ValueError("Invalid aggregation method. "\
  1405. "Choose 'average' for multitasking.")
  1406. input_gain = None
  1407. l2_alpha = None
  1408. pruning_ratio = None
  1409. scores = []
  1410. for scores_niter in self.hyperparameter_results:
  1411. if scores_niter is None:
  1412. scores.append(np.nan)
  1413. continue
  1414. scores_type = []
  1415. for net_type in range(n_net_types):
  1416. max_scores = []
  1417. for seed in range(nseeds):
  1418. #max score across alpha values
  1419. max_scores.append(scores_niter[seed][net_type].values.max())
  1420. #mean score across seeds
  1421. scores_type.append(np.mean(max_scores))
  1422. #max score across network types
  1423. scores.append(np.max(scores_type))
  1424. #best hyperparameter
  1425. if gain_opt:
  1426. self.input_gain = self.param_sampler[np.nanargmax(scores)]['input_gain']
  1427. input_gain = self.input_gain
  1428. if ridge_opt:
  1429. self.l2_alpha = self.param_sampler[np.nanargmax(scores)]['l2_alpha']
  1430. l2_alpha = self.l2_alpha
  1431. if density_opt:
  1432. self.pruning_ratio = self.param_sampler[np.nanargmax(scores)]['pruning_ratio']
  1433. pruning_ratio = self.pruning_ratio
  1434. return input_gain, l2_alpha, pruning_ratio

task.py at commit 28c1327, under BSD-3-Clause · at the source

Overview

Authors: Filip Milisav1, Andrea I. Luppi1,2,3, Laura E. Suárez4, Guillaume Lajoie4,5, Bratislav Misic1
  1. Montreal Neurological Institute, McGill University,Montréal, QC Canada
  2. Department of Psychiatry, University of Oxford,Oxford, UK
  3. St John’s College, University of Cambridge,Cambridge, UK
  4. Mila—Quebec Artificial Intelligence Institute,Montréal, QC Canada
  5. Department of Mathematics And Statistics, Université de Montréal,Montréal, QC Canada
Journal: Nature communications, volume 17, issue 1, article 7962
Dates: received 11 August 2025; accepted 5 June 2026; published online 25 June 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1038/s41467-026-74466-2 · PMID 42350431 · PMCID PMC13448079 · OpenAlex W4411509350
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: human (organism), computational (subfield)
Methods: Preprocessing, Connectivity, Statistics, Spectral & time-frequency, Graphs, Machine learning, Smoothing, state filtering, decompositions, fMRI & imaging
Keywords: Network models, Dynamical systems
MeSH: Brain*, Models, Neurological*, Nerve Net*, Neural Networks, Computer*, Computer Simulation, Humans, Memory, Recurrent Neural Networks (* major topic)
Topic: Advanced Memory and Neural Computing (Electrical and Electronic Engineering, Engineering), according to OpenAlex
Funding: Natural Sciences and Engineering Research Council of Canada; Canadian Institutes of Health Research; Brain Canada Foundation; Canada Research Chairs (CRC); Michael J. Fox Foundation for Parkinson’s Research; Healthy Brains for Healthy Lives initiative; Fonds de Recherche du Québec - Nature et Technologies (Quebec Fund for Research in Nature and Technology); Healthy Brains for Healthy Lives initiative Centre Union Neurosciences and Artificial Intelligence—Quebec; Wellcome Trust (Wellcome) (226924/Z/23/Z); St. John’s College, Cambridge; Canada CIFAR AI Chair Canada Research Chair in Neural Computations and Interfacing
Citations: cited by 2 papers (Europe PMC); 217 references in the paper

Abstract

Modularity is a fundamental principle of brain organization, reflected in the presence of segregated subnetworks that enable specialized information processing. These densely connected modules are often nested within larger, higher-order modules, giving rise to a hierarchical modular architecture. Yet, how hierarchical modularity shapes network function remains unclear. Here we introduce a simple blockmodeling framework for generating multi-level hierarchical modular networks and implement them as recurrent neural network reservoirs to evaluate their computational capacity. We show that hierarchical modular networks enhance memory capacity, support multitasking, and produce a broader range of temporal dynamics compared to strictly modular and random networks. These functional advantages can be traced to topological features enriched in hierarchical modular networks, including reciprocal and cyclic network motifs. We find that these benefits extend to the heterogeneous modular organization of empirical human brain structural connectivity, where hierarchical organization enhances memory capacity and contributes to the emergence of brain-like neural timescales. Altogether, these results show that hierarchical modularity endows networks with computationally advantageous properties, providing insight into the relationship between neural network structure and function.

Reproduced under the paper's license (CC BY), from the paper cited above.

Repositories

Its files are read in the Code ↔ Paper reader above, with 16 matches between paragraphs and lines of code.

netneurolab/conn2res

License: BSD-3-Clause
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 3ccb7074261c910847dcd0164b00ff3b02bade90, 20 December 2024
Languages: Python (24), Jupyter (1)
Size: 56 files, 25 scripts
Software Heritage: not archived
Found in: the text, “Reservoir computing”
Holds: README, license file, environment (pyproject.toml, requirements.txt, setup.cfg, setup.py, docs/requirements.txt), tests, documentation, 1 notebook
Not found: CITATION.cff, continuous integration
Tools: NumPy (18 files), pandas (11 files), scikit-learn (6 files), SciPy (5 files), Matplotlib (4 files), seaborn (4 files), Brain Connectivity Toolbox (1 file), NetworkX (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
27 files

netneurolab/netneurotools

License: BSD-3-Clause
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 49f83c023022ab606581cb10aec6a6282a306c48, 31 July 2026
Languages: Python (83), Shell (3)
Size: 118 files, 86 scripts
Software Heritage: archived
Found in: the text, “Data acquisition and connectome reconstruction”
Holds: README, license file, environment (Dockerfile, pyproject.toml, requirements.txt, setup.py, docs/requirements.txt), tests, continuous integration, documentation
Not found: CITATION.cff
Tools: NumPy (41 files), netneurotools (26 files), scikit-learn (11 files), SciPy (10 files), Matplotlib (9 files), NiBabel (8 files), Numba (8 files), Brain Connectivity Toolbox (3 files), neuromaps (1 file), Nilearn (1 file), pandas (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
88 files

netneurolab/milisav_hierarchical_modularity

License: BSD-3-Clause
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 28c132738775dc0b72802dfb20fa346320ef08ec, 7 April 2026
Languages: Python (13), Jupyter (10), MATLAB (1)
Size: 42 files, 24 scripts
Software Heritage: not archived
Found in: “Code availability”
Holds: README, license file, 10 notebooks
Not found: CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (16 files), Matplotlib (12 files), SciPy (9 files), seaborn (9 files), Brain Connectivity Toolbox (8 files), pandas (6 files), netneurotools (4 files), NetworkX (4 files), scikit-learn (2 files), neuromaps (1 file), Pingouin (1 file), statsmodels (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
26 files

Zenodo 20360160

License: BSD-3-Clause
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Size: 1 file
Software Heritage: not checked
Found in: the references
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (16 files), Matplotlib (12 files), SciPy (9 files), seaborn (9 files), Brain Connectivity Toolbox (8 files), pandas (6 files), netneurotools (4 files), NetworkX (4 files), scikit-learn (2 files), neuromaps (1 file), Pingouin (1 file), statsmodels (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers (HTTP 200)
  • 27 September 2026: the link answers (HTTP 200)
26 files
At the source:

Code availability

The Python code used to perform the experiments and generate the figures presented in this manuscript is available at https://github.com/netneurolab/milisav_hierarchical_modularity217.

Reproduced under the paper's license (CC BY), from the paper cited above.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 4 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 159 scripts, each with its path and the digest of its content;
  • 16 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

Datasets cited

Data availability

Data used in this study is available at https://github.com/netneurolab/milisav_hierarchical_modularity217. The original HCP dataset120 is available at https://db.humanconnectome.org/data/projects/HCP_1200.

Reproduced under the paper's license (CC BY), from the paper cited above.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 5 authors, 2 keywords, 8 MeSH terms, 11 funders, 172 references.

Cite

This paper

Milisav, F., Luppi, A. I., Suárez, L. E., Lajoie, G., & Misic, B. (2026). Neuromorphic hierarchical modular reservoirs. Nature communications, 17(1), 7962. https://doi.org/10.1038/s41467-026-74466-2

BibTeX

@article{milisav2026neuromorphic,
author = {Milisav, Filip and Luppi, Andrea I. and Suárez, Laura E. and Lajoie, Guillaume and Misic, Bratislav},
title = {{Neuromorphic hierarchical modular reservoirs}},
journal = {Nature communications},
year = {2026},
month = jun,
volume = {17},
number = {1},
pages = {7962},
publisher = {Nature Publishing Group},
issn = {2041-1723},
doi = {10.1038/s41467-026-74466-2},
url = {https://doi.org/10.1038/s41467-026-74466-2},
pmid = {42350431},
pmcid = {PMC13448079}
}

RIS

TY - JOUR
AU - Milisav, Filip
AU - Luppi, Andrea I.
AU - Suárez, Laura E.
AU - Lajoie, Guillaume
AU - Misic, Bratislav
TI - Neuromorphic hierarchical modular reservoirs
T2 - Nature communications
J2 - Nat Commun
PY - 2026
DA - 2026/06/25
VL - 17
IS - 1
SP - 7962
SN - 2041-1723
PB - Nature Publishing Group
DO - 10.1038/s41467-026-74466-2
UR - https://doi.org/10.1038/s41467-026-74466-2
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s41467-026-74466-2",
"type": "article-journal",
"title": "Neuromorphic hierarchical modular reservoirs",
"container-title": "Nature communications",
"author": [
{
"family": "Milisav",
"given": "Filip"
},
{
"family": "Luppi",
"given": "Andrea I."
},
{
"family": "Suárez",
"given": "Laura E."
},
{
"family": "Lajoie",
"given": "Guillaume"
},
{
"family": "Misic",
"given": "Bratislav"
}
],
"container-title-short": "Nat Commun",
"volume": "17",
"issue": "1",
"page": "7962",
"DOI": "10.1038/s41467-026-74466-2",
"PMID": "42350431",
"PMCID": "PMC13448079",
"ISSN": "2041-1723",
"publisher": "Nature Publishing Group",
"URL": "https://doi.org/10.1038/s41467-026-74466-2",
"language": "en",
"issued": {
"date-parts": [
[
2026,
6,
25
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1038/s41593-026-02205-3 [code]
Competitive interactions shape mammalian brain network dynamics and computation.
Journal: Nature neuroscience
In common: Brain Connectivity Toolbox, NetworkX, NiBabel, 6 other tools, 12 references, 3 authors
[2] doi:10.1126/sciadv.aef2894 [code]
Human cortical networks trade communication efficiency for computational reliability.
Journal: Science advances
In common: netneurotools, seaborn, SciPy, 2 other tools, 15 references, author Andrea I Luppi
[3] doi:10.7554/elife.103097 [code]
Canonical neurodevelopmental trajectories of structural and functional manifolds.
Journal: eLife
In common: netneurotools, neuromaps, Brain Connectivity Toolbox, 8 other tools, 8 references
[4] doi:10.1162/imag.a.1248 [code]
Estimating fMRI timescale maps.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: neuromaps, Nilearn, NiBabel, 6 other tools, 11 references
[5] doi:10.1038/s41540-026-00727-x [code]
Association-sensory spatiotemporal hierarchy and functional gradient-regularised recurrent neural network with implications for schizophrenia.
Journal: NPJ systems biology and applications
In common: statsmodels, seaborn, pandas, 3 other tools, computational, 12 references
[6] doi:10.1038/s41467-026-75959-w [code]
Charting higher-order models of brain function beyond pairwise interactions.
Journal: Nature communications
In common: neuromaps, Numba, Nilearn, 9 other tools, 8 references
[7] doi:10.1371/journal.pbio.3003856 [code]
Aging and metabolism contribute separately to brain-body health.
Journal: PLoS biology
In common: netneurotools, neuromaps, Nilearn, 8 other tools, 5 references, author Bratislav Misic
[8] doi:10.1038/s42003-025-09444-3 [code]
Decoupling of neurophysiological activity from structure mirrors global microarchitectural and neuromodulatory trends.
Journal: Communications biology
In common: netneurotools, neuromaps, Brain Connectivity Toolbox, 8 other tools, 6 references
[9] doi:10.1016/j.nicl.2026.104012 [code]
Structural-functional multilayer brain network properties and outcome of combined repetitive transcranial magnetic stimulation and psychotherapy for obsessive-compulsive disorder.
Journal: NeuroImage. Clinical
In common: Brain Connectivity Toolbox, Pingouin, NetworkX, 8 other tools, 5 references
[10] doi:10.1038/s41398-026-04025-2 [code]
Brain energetic landscapes shape state dysregulation in major depressive disorder: a morphological network controllability perspective.
Journal: Translational psychiatry
In common: neuromaps, Brain Connectivity Toolbox, Pingouin, 8 other tools, 4 references

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.