bmtool 0.5.1__py3-none-any.whl → 0.5.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
bmtool/bmplot.py CHANGED
@@ -3,7 +3,6 @@ Want to be able to take multiple plot names in and plot them all at the same tim
3
3
  https://stackoverflow.com/questions/458209/is-there-a-way-to-detach-matplotlib-plots-so-that-the-computation-can-continue
4
4
  """
5
5
  from .util import util
6
-
7
6
  import argparse,os,sys
8
7
 
9
8
  import numpy as np
@@ -82,7 +81,6 @@ def total_connection_matrix(config=None,title=None,sources=None, targets=None, s
82
81
  title = "All Synapse .mod Files Used"
83
82
  if synaptic_info=='3':
84
83
  title = "All Synapse .json Files Used"
85
-
86
84
  plot_connection_info(text,num,source_labels,target_labels,title, syn_info=synaptic_info, save_file=save_file)
87
85
  return
88
86
 
@@ -198,7 +196,7 @@ def probability_connection_matrix(config=None,nodes=None,edges=None,title=None,s
198
196
 
199
197
  return
200
198
 
201
- def convergence_connection_matrix(config=None,title=None,sources=None, targets=None, sids=None, tids=None, no_prepend_pop=False,save_file=None,convergence=True,method='mean',include_gap=True):
199
+ def convergence_connection_matrix(config=None,title=None,sources=None, targets=None, sids=None, tids=None, no_prepend_pop=False,save_file=None,convergence=True,method='mean+std',include_gap=True):
202
200
  """
203
201
  Generates connection plot displaying convergence data
204
202
  config: A BMTK simulation config
@@ -208,7 +206,7 @@ def convergence_connection_matrix(config=None,title=None,sources=None, targets=N
208
206
  tids: target node identifier
209
207
  no_prepend_pop: dictates if population name is displayed before sid or tid when displaying graph
210
208
  save_file: If plot should be saved
211
- method: 'mean','min','max','stdev' for connvergence plot
209
+ method: 'mean','min','max','stdev' or 'mean+std' connvergence plot
212
210
  """
213
211
  if not config:
214
212
  raise Exception("config not defined")
@@ -216,7 +214,7 @@ def convergence_connection_matrix(config=None,title=None,sources=None, targets=N
216
214
  raise Exception("Sources or targets not defined")
217
215
  return divergence_connection_matrix(config,title ,sources, targets, sids, tids, no_prepend_pop, save_file ,convergence, method,include_gap=include_gap)
218
216
 
219
- def divergence_connection_matrix(config=None,title=None,sources=None, targets=None, sids=None, tids=None, no_prepend_pop=False,save_file=None,convergence=False,method='mean',include_gap=True):
217
+ def divergence_connection_matrix(config=None,title=None,sources=None, targets=None, sids=None, tids=None, no_prepend_pop=False,save_file=None,convergence=False,method='mean+std',include_gap=True):
220
218
  """
221
219
  Generates connection plot displaying divergence data
222
220
  config: A BMTK simulation config
@@ -226,7 +224,7 @@ def divergence_connection_matrix(config=None,title=None,sources=None, targets=No
226
224
  tids: target node identifier
227
225
  no_prepend_pop: dictates if population name is displayed before sid or tid when displaying graph
228
226
  save_file: If plot should be saved
229
- method: 'mean','min','max','stdev' for connvergence plot
227
+ method: 'mean','min','max','stdev', and 'mean+std' for divergence plot
230
228
  """
231
229
  if not config:
232
230
  raise Exception("config not defined")
@@ -256,17 +254,94 @@ def divergence_connection_matrix(config=None,title=None,sources=None, targets=No
256
254
  title = "Maximum "
257
255
  elif method == 'std':
258
256
  title = "Standard Deviation "
259
- else:
257
+ elif method == 'mean':
260
258
  title = "Mean "
259
+ else:
260
+ title = 'Mean + Std '
261
261
 
262
262
  if convergence:
263
263
  title = title + "Synaptic Convergence"
264
264
  else:
265
265
  title = title + "Synaptic Divergence"
266
-
267
- plot_connection_info(data,data,source_labels,target_labels,title, save_file=save_file)
266
+ plot_connection_info(syn_info,data,source_labels,target_labels,title, save_file=save_file)
268
267
  return
269
268
 
269
+ def gap_junction_matrix(config=None,title=None,sources=None, targets=None, sids=None,tids=None, no_prepend_pop=False,save_file=None,type='convergence'):
270
+ """
271
+ Generates connection plot displaying gap junction data.
272
+ config: A BMTK simulation config
273
+ sources: network name(s) to plot
274
+ targets: network name(s) to plot
275
+ sids: source node identifier
276
+ tids: target node identifier
277
+ no_prepend_pop: dictates if population name is displayed before sid or tid when displaying graph
278
+ save_file: If plot should be saved
279
+ type:'convergence' or 'percent' connections
280
+ """
281
+ if not config:
282
+ raise Exception("config not defined")
283
+ if not sources or not targets:
284
+ raise Exception("Sources or targets not defined")
285
+ if type !='convergence' and type!='percent':
286
+ raise Exception("type must be 'convergence' or 'percent'")
287
+ sources = sources.split(",")
288
+ targets = targets.split(",")
289
+ if sids:
290
+ sids = sids.split(",")
291
+ else:
292
+ sids = []
293
+ if tids:
294
+ tids = tids.split(",")
295
+ else:
296
+ tids = []
297
+ syn_info, data, source_labels, target_labels = util.gap_junction_connections(config=config,nodes=None,edges=None,sources=sources,targets=targets,sids=sids,tids=tids,prepend_pop=not no_prepend_pop,type=type)
298
+
299
+
300
+ def filter_rows(syn_info, data, source_labels, target_labels):
301
+ new_syn_info = syn_info
302
+ new_data = data
303
+ new_source_labels = source_labels
304
+ new_target_labels = target_labels
305
+ for row in new_data:
306
+ row_index = -1
307
+ try:
308
+ if((np.isnan(row).all())): #checks if all of a row is nan
309
+ row_index = np.where(np.isnan(new_data)==np.isnan(row))[0][0]
310
+ except:
311
+ row_index = -1
312
+ finally:
313
+ if(all(x==0 for x in row)): #checks if all of a row is zeroes
314
+ row_index = np.where(new_data==row)[0][0]
315
+ if row_index!=-1: #deletes corresponding row accordingly in all relevant variables.
316
+ new_syn_info = np.delete(new_syn_info,row_index,0)
317
+ new_data = np.delete(new_data,row_index,0)
318
+ new_source_labels = np.delete(new_source_labels,row_index)
319
+ return new_syn_info, new_data,new_source_labels,new_target_labels
320
+
321
+ def filter_rows_and_columns(syn_info,data,source_labels,target_labels):
322
+ syn_info, data, source_labels, target_labels = filter_rows(syn_info, data, source_labels, target_labels)
323
+ transposed_syn_info = np.transpose(syn_info) #transpose everything and put it in to make sure columns get filtered
324
+ transposed_data = np.transpose(data)
325
+ transposed_source_labels = target_labels
326
+ transposed_target_labels = source_labels
327
+ syn_info, data, source_labels, target_labels = filter_rows(transposed_syn_info, transposed_data, transposed_source_labels, transposed_target_labels)
328
+ filtered_syn_info = np.transpose(syn_info) #transpose everything back to original order after filtering.
329
+ filtered_data = np.transpose(data)
330
+ filtered_source_labels = target_labels
331
+ filtered_target_labels = source_labels
332
+ return filtered_syn_info,filtered_data,filtered_source_labels,filtered_target_labels
333
+
334
+ syn_info, data, source_labels, target_labels = filter_rows_and_columns(syn_info, data, source_labels, target_labels)
335
+
336
+ if title == None or title=="":
337
+ title = 'Gap Junction'
338
+ if type == 'convergence':
339
+ title+=' Syn Convergence'
340
+ elif type == 'percent':
341
+ title+=' Percent Connectivity'
342
+ plot_connection_info(syn_info,data,source_labels,target_labels,title, save_file=save_file)
343
+ return
344
+
270
345
  def connection_histogram(config=None,nodes=None,edges=None,sources=[],targets=[],sids=[],tids=[],prepend_pop=True,synaptic_info='0',
271
346
  source_cell = None,target_cell = None,include_gap=True):
272
347
  """
bmtool/connectors.py CHANGED
@@ -4,6 +4,7 @@ from scipy.special import erf
4
4
  from scipy.optimize import minimize_scalar
5
5
  from functools import partial
6
6
  import time
7
+ import types
7
8
 
8
9
  rng = np.random.default_rng()
9
10
 
@@ -11,6 +12,13 @@ rng = np.random.default_rng()
11
12
  ############################## CONNECT CELLS #################################
12
13
 
13
14
  # Utility Functions
15
+ def num_prop(ratio, N):
16
+ """Calculate numbers of total N in proportion to ratio"""
17
+ ratio = np.asarray(ratio)
18
+ p = np.cumsum(np.insert(ratio.ravel(), 0, 0)) # cumulative proportion
19
+ return np.diff(np.round(N / p[-1] * p).astype(int)).reshape(ratio.shape)
20
+
21
+
14
22
  def decision(prob, size=None):
15
23
  """
16
24
  Make single random decision based on input probability.
@@ -76,10 +84,10 @@ class DistantDependentProbability(ProbabilityFunction):
76
84
  assert(min_dist >= 0 and min_dist < max_dist)
77
85
  self.min_dist, self.max_dist = min_dist, max_dist
78
86
 
79
- def __call__(self, dist):
87
+ def __call__(self, dist, *arg, **kwargs):
80
88
  """Return probability for single distance input"""
81
89
  if dist >= self.min_dist and dist <= self.max_dist:
82
- return self.probability(dist).item()
90
+ return self.probability(dist)
83
91
  else:
84
92
  return 0.
85
93
 
@@ -88,7 +96,10 @@ class DistantDependentProbability(ProbabilityFunction):
88
96
  dist = np.asarray(dist)
89
97
  dec = np.zeros(dist.shape, dtype=bool)
90
98
  mask = (dist >= self.min_dist) & (dist <= self.max_dist)
91
- dec[mask] = decisions(self.probability(dist[mask]))
99
+ dist = dist[mask]
100
+ prob = np.empty(dist.shape)
101
+ prob[:] = self.probability(dist)
102
+ dec[mask] = decisions(prob)
92
103
  return dec
93
104
 
94
105
 
@@ -99,7 +110,7 @@ class UniformInRange(DistantDependentProbability):
99
110
  super().__init__(min_dist=min_dist, max_dist=max_dist)
100
111
  self.p = np.array(p)
101
112
  assert(self.p.size == 1)
102
- assert(self.p >= 0. and self.p <= 1.)
113
+ assert(p >= 0. and p <= 1.)
103
114
 
104
115
  def probability(self, dist):
105
116
  return self.p
@@ -127,7 +138,7 @@ class GaussianDropoff(DistantDependentProbability):
127
138
  input pmax, and calculate pmax. See calc_pmax_from_ptotal() method.
128
139
  ptotal_dist_range: Distance range for calculating pmax when ptotal is
129
140
  specified. If not specified, set to range (min_dist, max_dist).
130
- dist_type: spherical or cylindrical for distance metric.
141
+ dist_type: 'spherical' or 'cylindrical' for distance metric.
131
142
  Used when ptotal is specified.
132
143
 
133
144
  Returns:
@@ -215,6 +226,51 @@ class GaussianDropoff(DistantDependentProbability):
215
226
  self.probability = probability
216
227
 
217
228
 
229
+ class NormalizedReciprocalRate(ProbabilityFunction):
230
+ """Reciprocal connection probability given normalized reciprocal rate.
231
+ Normalized reciprocal rate is defined as the ratio between the reciprocal
232
+ connection probability and the connection probability for a randomly
233
+ connected network where the two unidirectional connections between any pair
234
+ of neurons are independent. NRR = pr / (p0 * p1)
235
+
236
+ Parameters:
237
+ NRR: a constant or distance dependent function for normalized reciprocal
238
+ rate. When being a function, it should be accept vectorized input.
239
+ Returns:
240
+ A callable object that returns the probability value.
241
+ """
242
+
243
+ def __init__(self, NRR=1.):
244
+ self.NRR = NRR if callable(NRR) else lambda *x: NRR
245
+
246
+ def probability(self, dist, p0, p1):
247
+ """Allow numpy array input and return probability in numpy array"""
248
+ return p0 * p1 * self.NRR(dist)
249
+
250
+ def __call__(self, dist, p0, p1, *arg, **kwargs):
251
+ """Return probability for single distance input"""
252
+ return self.probability(dist, p0, p1)
253
+
254
+ def decisions(self, dist, p0, p1, cond=None):
255
+ """Return bool array of decisions
256
+ dist: distance (scalar or array). Will be ignored if NRR is constant.
257
+ p0, p1: forward and backward probability (scalar or array)
258
+ cond: A tuple (direction, array of outcomes) representing the condition.
259
+ Conditional probability will be returned if specified. The condition
260
+ event is determined by connection direction (0 for forward, or 1 for
261
+ backward) and outcomes (bool array of whether connection exists).
262
+ """
263
+ dist, p0, p1 = map(np.asarray, (dist, p0, p1))
264
+ pr = np.empty(dist.shape)
265
+ pr[:] = self.probability(dist, p0, p1)
266
+ pr = np.clip(pr, a_min=np.fmax(p0 + p1 - 1., 0.), a_max=np.fmin(p0, p1))
267
+ if cond is not None:
268
+ mask = np.asarray(cond[1])
269
+ pr[mask] /= p1 if cond[0] else p0
270
+ pr[~mask] = 0.
271
+ return decisions(pr)
272
+
273
+
218
274
  # Connector Classes
219
275
  class AbstractConnector(ABC):
220
276
  """Abstract base class for connectors"""
@@ -231,22 +287,6 @@ class AbstractConnector(ABC):
231
287
  `connection_rule` method."""
232
288
  return NotImplemented
233
289
 
234
- @staticmethod
235
- def is_same_pop(source, target, quick=True):
236
- """Whether two NodePool objects direct to the same population"""
237
- if quick:
238
- # Quick check (compare filter conditions)
239
- same = (source.network_name == target.network_name and
240
- source._NodePool__properties ==
241
- target._NodePool__properties)
242
- else:
243
- # Strict check (compare all nodes)
244
- same = (source.network_name == target.network_name and
245
- len(source) == len(target) and
246
- all([s.node_id == t.node_id
247
- for s, t in zip(source, target)]))
248
- return same
249
-
250
290
  @staticmethod
251
291
  def constant_function(val):
252
292
  """Convert a constant to a constant function"""
@@ -255,7 +295,23 @@ class AbstractConnector(ABC):
255
295
  return constant
256
296
 
257
297
 
258
- # Helper class
298
+ # Helper functions
299
+ def is_same_pop(source, target, quick=False):
300
+ """Check whether two NodePool objects direct to the same population"""
301
+ if quick:
302
+ # Quick check (compare filter conditions)
303
+ same = (source.network_name == target.network_name and
304
+ source._NodePool__properties ==
305
+ target._NodePool__properties)
306
+ else:
307
+ # Strict check (compare all nodes)
308
+ same = (source.network_name == target.network_name and
309
+ len(source) == len(target) and
310
+ all([s.node_id == t.node_id
311
+ for s, t in zip(source, target)]))
312
+ return same
313
+
314
+
259
315
  class Timer(object):
260
316
  def __init__(self, unit='sec'):
261
317
  if unit == 'ms':
@@ -303,7 +359,7 @@ def rho_2_pr(p0, p1, rho):
303
359
 
304
360
  class ReciprocalConnector(AbstractConnector):
305
361
  """
306
- Object for building connections in bmtk network model with reciprocal
362
+ Object for buiilding connections in bmtk network model with reciprocal
307
363
  probability within a single population (or between two populations).
308
364
 
309
365
  Algorithm:
@@ -388,10 +444,17 @@ class ReciprocalConnector(AbstractConnector):
388
444
  symmetric_p1_arg: Whether p0_arg and p1_arg are identical. If this is
389
445
  set to True, argument p1_arg will be ignored. This is forced to be
390
446
  True when the population is recurrent.
391
- pr, pr_arg: Probability of reciprocal connection and its input argument
392
- when it is a function, similar to p0, p0_arg, p1, p1_arg. It can be
393
- a function when it has an explicit relation with some node
394
- properties such as distance.
447
+ pr, pr_arg: Probability of reciprocal connection and its first input
448
+ argument when it is a function, similar to p0, p0_arg, p1, p1_arg.
449
+ It can be a function when it has an explicit relation with some node
450
+ properties such as distance. A function pr requires two additional
451
+ positional arguments p0 and p1 even if they are not used, i.e.,
452
+ pr(pr_arg, p0, p1), just in case pr is dependent on p0 and p1, e.g.,
453
+ when normalized reciprocal rate NRR = pr/(p0*p1) is given.
454
+ When pr_arg is a string, the same value as p1_arg will be used for
455
+ pr_arg if the string contains '1', e.g., '1', 'p1'. Otherwise, e.g.,
456
+ '', '0', 'p0', p0_arg will be used for pr_arg. Specifying this can
457
+ avoid recomputing pr_arg when it's given by p0_arg or p1_arg.
395
458
  estimate_rho: Whether estimate rho that result in an overall pr. This
396
459
  is forced to be False if pr is a function or if rho is specified.
397
460
  To estimate rho, all the pairs with possible connections, meaning
@@ -425,6 +488,10 @@ class ReciprocalConnector(AbstractConnector):
425
488
  into the connection matrix to reduce memory consumption.
426
489
  autapses: Whether to allow connecting a cell to itself. Default: False.
427
490
  This is ignored when the population is not recurrent.
491
+ quick_pop_check: Whether to use quick method to check if source and
492
+ target populations are the same. Default: False.
493
+ Quick method checks only whether filter conditions match.
494
+ Strict method checks whether all node id's match considering order.
428
495
  cache_data: Whether to cache the values of p0, p0_arg, p1, p1_arg
429
496
  during estimation of rho. This improves performance when
430
497
  estimate_rho is True while not creating a significant overhead in
@@ -465,7 +532,7 @@ class ReciprocalConnector(AbstractConnector):
465
532
  pr=0., pr_arg=None, estimate_rho=True, rho=None,
466
533
  dist_range_forward=None, dist_range_backward=None,
467
534
  n_syn0=1, n_syn1=1, autapses=False,
468
- cache_data=True, verbose=True):
535
+ quick_pop_check=False, cache_data=True, verbose=True):
469
536
  args = locals()
470
537
  var_set = ('p0', 'p0_arg', 'p1', 'p1_arg',
471
538
  'pr', 'pr_arg', 'n_syn0', 'n_syn1')
@@ -480,6 +547,7 @@ class ReciprocalConnector(AbstractConnector):
480
547
  self.rho = rho
481
548
 
482
549
  self.autapses = autapses
550
+ self.quick = quick_pop_check
483
551
  self.cache = self.ConnectorCache(cache_data and self.estimate_rho)
484
552
  self.verbose = verbose
485
553
 
@@ -493,8 +561,8 @@ class ReciprocalConnector(AbstractConnector):
493
561
  if self.stage:
494
562
  # check whether the correct populations
495
563
  if (source is None or target is None or
496
- not self.is_same_pop(source, self.target) or
497
- not self.is_same_pop(target, self.source)):
564
+ not is_same_pop(source, self.target, quick=self.quick) or
565
+ not is_same_pop(target, self.source, quick=self.quick)):
498
566
  raise ValueError("Source or target population not consistent.")
499
567
  # Skip adding nodes for the backward stage.
500
568
  return
@@ -508,7 +576,7 @@ class ReciprocalConnector(AbstractConnector):
508
576
  raise ValueError("Target nodes do not exists")
509
577
 
510
578
  # Setup nodes
511
- self.recurrent = self.is_same_pop(self.source, self.target, quick=True)
579
+ self.recurrent = is_same_pop(self.source, self.target, quick=self.quick)
512
580
  self.source_ids = [s.node_id for s in self.source]
513
581
  self.n_source = len(self.source_ids)
514
582
  self.source_list = list(self.source)
@@ -551,6 +619,7 @@ class ReciprocalConnector(AbstractConnector):
551
619
  self.enable = enable
552
620
  self._output = {}
553
621
  self.cache_dict = {}
622
+ self.set_next_it()
554
623
  self.write_mode()
555
624
 
556
625
  def cache_output(self, func, func_name, cache=True):
@@ -606,9 +675,14 @@ class ReciprocalConnector(AbstractConnector):
606
675
  self.enable = False
607
676
  self.mode = 'read'
608
677
 
609
- def next_it(self):
678
+ def set_next_it(self):
610
679
  if self.enable:
611
- self.iter_count += 1
680
+ def next_it():
681
+ self.iter_count += 1
682
+ else:
683
+ def next_it():
684
+ pass
685
+ self.next_it = next_it
612
686
 
613
687
  def node_2_idx_input(self, var_func, reverse=False):
614
688
  """Convert a function that accept nodes as input
@@ -703,6 +777,13 @@ class ReciprocalConnector(AbstractConnector):
703
777
 
704
778
  # *** A sequence of major methods executed during build ***
705
779
  def setup_variables(self):
780
+ # If pr_arg is string, use the same value as p0_arg or p1_arg
781
+ if isinstance(self.vars['pr_arg'], str):
782
+ pr_arg_func = 'p1_arg' if '1' in self.vars['pr_arg'] else 'p0_arg'
783
+ self.vars['pr_arg'] = self.vars[pr_arg_func]
784
+ else:
785
+ pr_arg_func = None
786
+
706
787
  callable_set = set()
707
788
  # Make constant variables constant functions
708
789
  for name, var in self.vars.items():
@@ -718,6 +799,21 @@ class ReciprocalConnector(AbstractConnector):
718
799
  var = self.vars[name]
719
800
  setattr(self, name, self.node_2_idx_input(var, '1' in name))
720
801
 
802
+ # Set up function for pr_arg if use value from p0_arg or p1_arg
803
+ if pr_arg_func is None:
804
+ self._pr_arg = self.pr_arg # use specified pr_arg
805
+ else:
806
+ self._pr_arg_val = 0. # storing current value from p_arg
807
+ p_arg = getattr(self, pr_arg_func)
808
+ def p_arg_4_pr(*args, **kwargs):
809
+ val = p_arg(*args, **kwargs)
810
+ self._pr_arg_val = val
811
+ return val
812
+ setattr(self, pr_arg_func, p_arg_4_pr)
813
+ def pr_arg(self, *arg):
814
+ return self._pr_arg_val
815
+ self._pr_arg = types.MethodType(pr_arg, self)
816
+
721
817
  def cache_variables(self):
722
818
  # Select cacheable attrilbutes
723
819
  cache_set = {'p0', 'p0_arg', 'p1', 'p1_arg'}
@@ -825,7 +921,7 @@ class ReciprocalConnector(AbstractConnector):
825
921
  if forward:
826
922
  forward = decision(p0)
827
923
  if backward:
828
- pr = self.pr(self.pr_arg(i, j))
924
+ pr = self.pr(self._pr_arg(i, j), p0, p1)
829
925
  backward = decision(self.cond_backward(forward, p0, p1, pr))
830
926
 
831
927
  # Make connection
@@ -849,7 +945,7 @@ class ReciprocalConnector(AbstractConnector):
849
945
  if self.verbose:
850
946
  self.timer.report('Total time for creating connection matrix')
851
947
  if self.wrong_pr:
852
- print("\nWarning: Value of 'pr' outside the bounds occurs.\n")
948
+ print("Warning: Value of 'pr' outside the bounds occurred.\n")
853
949
  self.connection_number_info()
854
950
 
855
951
  def make_connection(self):
@@ -949,7 +1045,7 @@ class ReciprocalConnector(AbstractConnector):
949
1045
 
950
1046
  class UnidirectionConnector(AbstractConnector):
951
1047
  """
952
- Object for building unidirectional connections in bmtk network model with
1048
+ Object for buiilding unidirectional connections in bmtk network model with
953
1049
  given probability within a single population (or between two populations).
954
1050
 
955
1051
  Parameters:
@@ -1081,9 +1177,80 @@ class UnidirectionConnector(AbstractConnector):
1081
1177
  % (100. * self.n_conn / self.n_pair))
1082
1178
 
1083
1179
 
1084
- class CorrelatedGapJunction(UnidirectionConnector):
1180
+ class GapJunction(UnidirectionConnector):
1181
+ """
1182
+ Object for buiilding gap junction connections in bmtk network model with
1183
+ given probabilities within a single population which is uncorrelated with
1184
+ the recurrent chemical synapses in this population.
1185
+
1186
+ Parameters:
1187
+ p, p_arg: Probability of forward connection and its input argument when
1188
+ it is a function, similar to p0, p0_arg in ReciprocalConnector. It
1189
+ can be a constant or a deterministic function whose value must be
1190
+ within range [0, 1]. When p is constant, the connection is
1191
+ homogenous.
1192
+ verbose: Whether show verbose information in console.
1193
+
1194
+ Returns:
1195
+ An object that works with BMTK to build edges in a network.
1196
+
1197
+ Important attributes:
1198
+ Similar to `UnidirectionConnector`.
1085
1199
  """
1086
- Object for building gap junction connections in bmtk network model with
1200
+
1201
+ def __init__(self, p=1., p_arg=None, verbose=True):
1202
+ super().__init__(p=p, p_arg=p_arg, verbose=verbose)
1203
+
1204
+ def setup_nodes(self, source=None, target=None):
1205
+ super().setup_nodes(source=source, target=target)
1206
+ if len(self.source) != len(self.target):
1207
+ raise ValueError("Source and target must be the same for "
1208
+ "gap junction.")
1209
+ self.n_source = len(self.source)
1210
+
1211
+ def make_connection(self, source, target, *args, **kwargs):
1212
+ """Assign gap junction per iteration using one_to_one iterator"""
1213
+ # Initialize in the first iteration
1214
+ if self.iter_count == 0:
1215
+ self.initialize()
1216
+ if self.verbose:
1217
+ src_str, _ = self.get_nodes_info()
1218
+ print("\nStart building gap junction \n in " + src_str)
1219
+
1220
+ # Consider each pair only once
1221
+ nsyns = 0
1222
+ i, j = divmod(self.iter_count, self.n_source)
1223
+ if i < j:
1224
+ p_arg = self.p_arg(source, target)
1225
+ p = self.p(p_arg)
1226
+ possible = p > 0
1227
+ self.n_poss += possible
1228
+ if possible and decision(p):
1229
+ nsyns = 1
1230
+ sid, tid = source.node_id, target.node_id
1231
+ self.add_conn_prop(sid, tid, p_arg)
1232
+ self.add_conn_prop(tid, sid, p_arg)
1233
+ self.n_conn += 1
1234
+
1235
+ self.iter_count += 1
1236
+
1237
+ # Detect end of iteration
1238
+ if self.iter_count == self.n_pair:
1239
+ if self.verbose:
1240
+ self.connection_number_info()
1241
+ self.timer.report('Done! \nTime for building connections')
1242
+ return nsyns
1243
+
1244
+ def connection_number_info(self):
1245
+ n_pair = self.n_pair
1246
+ self.n_pair = (n_pair - len(self.source)) // 2
1247
+ super().connection_number_info()
1248
+ self.n_pair = n_pair
1249
+
1250
+
1251
+ class CorrelatedGapJunction(GapJunction):
1252
+ """
1253
+ Object for buiilding gap junction connections in bmtk network model with
1087
1254
  given probabilities within a single population which could be correlated
1088
1255
  with the recurrent chemical synapses in this population.
1089
1256
 
@@ -1120,13 +1287,6 @@ class CorrelatedGapJunction(UnidirectionConnector):
1120
1287
  conn_prop = conn_prop[0]
1121
1288
  self.ref_conn_prop = conn_prop
1122
1289
 
1123
- def setup_nodes(self, source=None, target=None):
1124
- super().setup_nodes(source=source, target=target)
1125
- if len(self.source) != len(self.target):
1126
- raise ValueError("Source and target must be the same for "
1127
- "gap junction.")
1128
- self.n_source = len(self.source)
1129
-
1130
1290
  def conn_exist(self, sid, tid):
1131
1291
  trg_dict = self.ref_conn_prop.get(sid)
1132
1292
  if trg_dict is not None and tid in trg_dict:
@@ -1153,7 +1313,7 @@ class CorrelatedGapJunction(UnidirectionConnector):
1153
1313
  if self.iter_count == 0:
1154
1314
  self.initialize()
1155
1315
  if self.verbose:
1156
- src_str, trg_str = self.get_nodes_info()
1316
+ src_str, _ = self.get_nodes_info()
1157
1317
  print("\nStart building gap junction \n in " + src_str)
1158
1318
 
1159
1319
  # Consider each pair only once
@@ -1182,15 +1342,9 @@ class CorrelatedGapJunction(UnidirectionConnector):
1182
1342
  self.timer.report('Done! \nTime for building connections')
1183
1343
  return nsyns
1184
1344
 
1185
- def connection_number_info(self):
1186
- n_pair = self.n_pair
1187
- self.n_pair = (n_pair - len(self.source)) // 2
1188
- super().connection_number_info()
1189
- self.n_pair = n_pair
1190
-
1191
1345
 
1192
1346
  class OneToOneSequentialConnector(AbstractConnector):
1193
- """Object for building one to one correspondence connections in bmtk
1347
+ """Object for buiilding one to one correspondence connections in bmtk
1194
1348
  network model with between two populations. One of the population can
1195
1349
  consist of multiple sub-populations. These sub-populations need to be added
1196
1350
  sequentially using setup_nodes() and edge_params() methods followed by BMTK
@@ -1337,21 +1491,24 @@ FLUC_STDEV = 0.2 # ms
1337
1491
  DELAY_LOWBOUND = 0.2 # ms must be greater than h.dt
1338
1492
  DELAY_UPBOUND = 2.0 # ms
1339
1493
 
1340
- def syn_dist_delay_feng(source, target,
1341
- min_delay=SYN_MIN_DELAY, velocity=SYN_VELOCITY,
1342
- fluc_stdev=FLUC_STDEV, connector=None):
1494
+ def syn_dist_delay_feng(source, target, min_delay=SYN_MIN_DELAY,
1495
+ velocity=SYN_VELOCITY, fluc_stdev=FLUC_STDEV,
1496
+ delay_bound=(DELAY_LOWBOUND, DELAY_UPBOUND),
1497
+ connector=None):
1343
1498
  """Synpase delay linearly dependent on distance.
1344
1499
  min_delay: minimum delay (ms)
1345
1500
  velocity: synapse conduction velocity (micron/ms)
1346
1501
  fluc_stdev: standard deviation of random Gaussian fluctuation (ms)
1502
+ delay_bound: (lower, upper) bounds of delay (ms)
1503
+ connector: connector object from which to read distance
1347
1504
  """
1348
1505
  if connector is None:
1349
1506
  dist = euclid_dist(target['positions'], source['positions'])
1350
1507
  else:
1351
1508
  dist = connector.get_conn_prop(source.node_id, target.node_id)
1352
1509
  del_fluc = fluc_stdev * rng.normal()
1353
- delay = dist / SYN_VELOCITY + SYN_MIN_DELAY + del_fluc
1354
- delay = min(max(delay, DELAY_LOWBOUND), DELAY_UPBOUND)
1510
+ delay = dist / velocity + min_delay + del_fluc
1511
+ delay = min(max(delay, delay_bound[0]), delay_bound[1])
1355
1512
  return delay
1356
1513
 
1357
1514
 
@@ -1373,4 +1530,4 @@ def syn_dist_delay_feng_section_PN(source, target, p=0.9,
1373
1530
 
1374
1531
  def syn_uniform_delay_section(source, target, low=DELAY_LOWBOUND,
1375
1532
  high=DELAY_UPBOUND, **kwargs):
1376
- return rng.uniform(low, high)
1533
+ return rng.uniform(low, high)