-
Notifications
You must be signed in to change notification settings - Fork 0
/
GCM-n-neurites.py
2065 lines (1562 loc) · 79.9 KB
/
GCM-n-neurites.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import odeint
from matplotlib import colors as colors
from numpy import linalg as LA
from scipy import linalg as sciLA
from scipy.sparse import csr_matrix
import pandas as pd
def Hill(x, K, n):
# The Hill function
return x**n/(x**n+K**n)
def Heavi(x,K):
# The Heaviside function
if x>=K:
return 1.0
else:
return 0.0
def delta(x, h):
# The delta function for probability mass distribution
# This form of discrete delta is widely used in the immersed boundary method.
# Note that the lattice size h should be supplied, which is not necessary when used with LBM.
# Note that this is only the 1D delta. For 2D case, delta*delta should be used.
abs = np.abs
sqrt = np.sqrt
x0 = abs(x/h)
if x0<=1:
return (3 - 2*x0 + sqrt(1+4*x0-4*x0*x0))/8/h
elif x0<=2:
return (5 - 2*x0 - sqrt(-7+12*x0-4*x0*x0))/8/h
else:
return 0
class Pulse():
# Pulse (actin wave) class
# The basic parameters are pulse rate and amplitude.
# This is the pulse for a single equation. For the 2D case, currently both equations use the same pulse term. So the effective pulse rate is 2*rate
def __init__(self, pars=[1,1]):
amp, rate = pars
self.amp = amp
self.rate = rate
class Neurite():
# Neurite class
def __init__(self, pars):
beta, Kg, ng, r = pars # Note that the growth rate is denoted by ``beta'', which is ``g'' in the paper.
self.beta = beta
self.Kg = Kg
self.ng = ng
self.r = r
self.r0 = r # base retraction rate
def A(self, x):
# The RHS of the deterministic equation
# This function is not used anymore.
return self.beta*(x**self.n)/(x**self.n+self.K**self.n) - self.r*x
def plot_growth_retraction_terms(self):
# Plot beta*(x**n)/(x**n+K**n) and r*x separately
# This is used to show whether there is bistability in the deterministic part of the equation for a single neurite.
beta = self.beta
Kg = self.Kg
ng = self.ng
r = self.r
x_list = np.linspace(0, 2*Kg, 101)
growth_list = []
rL_list = []
for x in x_list:
growth_list.append(beta*Hill(x, Kg, ng))
rL_list.append(r*x)
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
ax.plot(x_list, growth_list, label='Growth')
ax.plot(x_list, rL_list, label='Retraction')
ax.legend(loc='best')
ax.set_xlabel('x')
ax.set_title(r"$\beta={}, K_g={:.2f}, n_g={}, r={}$".format(beta, Kg, ng, r))
class Cell():
# The lattice cell class
def __init__(self, pars):
label, pos, dx, pos_min, pos_max = pars # Extract input parameters
self.label = label # The label of a cell is a tuple of integers. The first cell is labeled as [0,0,...] Does it depend on the dimension????
self.pos = pos # The position of a cell is its center location
self.dx = dx # The increments in all directions
self.pos_min = pos_min # min position of the cell in all directions
self.pos_max = pos_max # max position of the cell in all directions
self.neighbors = [] # Neighboring cells, may not be used
self.d_to_target = 0 # Distance to a target position, used to spread prob mass
self.prob_spread = 0 # The probability mass to be spread, used to spread prob mass
# The number of end positions in this cell.
# Used when calculating Q
self.num_endpos = 0
class Probs():
# Probability class, used to store probability distribution in various forms
def __init__(self, dim):
self.dim = dim # Dimension of the system, equal to #neurites.
self.N = 0 # To store the total number of cells
self.cells = [] # Cell list
# The following three lists store prob distributions
# These are vectors. The cells are ordered in a certain way in order to get these vectors.
# To plot the distribution for the 2D case, the distribution must be transformed into a matrix.
self.P0 = [] # Probability list for the previous time step, may not be used
self.P1 = [] # Probability list
self.Ps = [] # Equilibirum distribution (the eigenvector corresponding to eigenvalue 1)
self.Q = [] # Transition matrix
self.P1_mat = [] # Matrix for the distribution at some time. This is for plotting in 2D case.
self.P0_mat = [] # Matrix for the initial distribution
self.Ps_mat = [] # Matrix for the stationary distribution. This is for plotting in 2D case.
self.map_labelpair_indexP = {} # The labels are those of the cells. The index refers to their location in self.P.
self.map_indexP_labelpair = {} # The inverse map of the previous one
self.Q_evals = [] # The eigenvalues of Q. They are used in finding stationary distributions and testing recurrency
self.Q_evects = [] # The eigenvectors of Q. Each column corresponds to an eigenvector.
self.Q_evects_inv = [] # The inverse of Q_evects, used in diagonalization.
self.Q_inf = [] # Q^{inf}: the stationary transition matrix
self.Q_n = [] # Q^n
self.Q_2expn = [] # Q^{2n} # This is used to get the final Q fast.
self.Q_absorb = [] # Modified Q according to the absorbing region, used to calculate FPT
# Sparse version of the matrices and vectors
# Used for fast multiplication
self.Q_sparse = []
self.P1_sparse = []
self.P0_sparse = []
# For Markov chain analysis
self.Q_list = []
# Transpose of Q
self.Q_T = []
# eps-committor
self.q_eps = []
# eps-committor field
self.q_eps_field = []
# Mean dwell time field, proportional to eps-committor field
self.mean_dwell_time = []
self.dt = 0 # Time step
self.t = 0 # Current time
self.neurites = [] # Neurite list
self.pulses = [] # Pulse list
self.xmin = [] # lower boundary for all directions
self.xmax = [] # upper boundary for all directions
self.dx = [] # increments for all directions
self.X = [] # Position mesh for 2D colormaps of Q and P_mat
self.Y = [] # Position mesh for 2D colormaps of Q and P_mat
self.num_cells = [] # Number of cells in all directions. Has this ever been used?????
# Index range for cells in all directions
# Note that if there are n cells in a direction, the index ranges from 0 to n-1
self.index_max = []
# The number of cells in two directions
self.n_cells = []
def set_dt(self, dt):
# Simply assign the value of dt
self.dt = dt
def set_range(self, xmin, xmax, dx):
# Set the range for all directions
# Note that these boundarys are vectors, which means the boudaries of all directions
# Even for 1D case, the pars should be supplied as lists.
self.xmin = xmin
self.xmax = xmax
self.dx = dx
# Calculate the index range for all directions
# self.index_max = [max index of the 1st direction, max index of the 2nd direction, ...]
# Note that if there are n cells in a direction, the index ranges from 0 to n-1
# The total length of all cells in a direction may be longer than xmax-xmin.
# For the last cell, maybe only part of it is within [xmin, xmax].(Here, [] means interval)
for i in range(self.dim):
self.index_max.append(int(np.ceil((self.xmax[i]-self.xmin[i])/self.dx[i]))-1)
# The number of cells in a direction is 1 larger than the max label.
self.n_cells.append(self.index_max[-1]+1)
def create_cells_n(self):
# Create cells
# Use alias to simplify codes
xmin = self.xmin
#xmax = self.xmax
dx =self.dx
index_max = self.index_max
# Create cells for 1D case (a single neurite)
if self.dim == 1:
for n in range(index_max[0]+1): # Note that index_max stores the maximum indices of cells in all directions. In order for n to reach index_max, we must use index_max[i]+1, not index_max.
label = (n,) # Generate the label for a cell. Pay attention to the format.
pos = [ xmin[0] + dx[0]/2 + n*dx[0],] # Generate the position for a cell. Pay attention to the format.
pos_min = [xmin[0] + n*dx[0],]
pos_max = [xmin[0] + dx[0] + n*dx[0],]
cell = Cell([label, pos, dx.copy()]) # Generate a cell
self.cells.append(cell) # Put the cell in the cell list
self.map_labelpair_indexP[label] = self.N # Add the label-index relation to the relation dictionary.
self.map_indexP_labelpair[self.N] = label # Add the index-label relation to the relation dictionary.
self.N += 1 # Increase the total number of cells by 1
# Create cells for 2D case (a single neurite)
# This is similar to the 1D case. Everything done in the 1D case is done twice here.
if self.dim == 2:
for n1 in range(self.index_max[1] + 1):
for n0 in range(self.index_max[0] + 1):
# Generate the cell label pair
label = (n0, n1)
# Calculate the center position
pos = []
pos.append(xmin[0] + dx[0]/2 + n0*dx[0])
pos.append(xmin[1] + dx[1]/2 + n1*dx[1])
# Calculate the lower boundary in each direction
pos_min = []
pos_min.append(xmin[0] + n0*dx[0])
pos_min.append(xmin[1] + n1*dx[1])
# Calculate the upper boundary in each direction
pos_max = []
pos_max.append(xmin[0] + dx[0] + n0*dx[0])
pos_max.append(xmin[1] + dx[1] + n1*dx[1])
# Generate a cell
cell = Cell([label, pos, dx.copy(), pos_min, pos_max])
# Add the cell to the cell list
self.cells.append(cell)
# Create maps between cell indices and label pairs
self.map_labelpair_indexP[label] = self.N
self.map_indexP_labelpair[self.N] = label
self.N += 1
def init_P_Q(self):
# Initialize vectors P0, P1 and Q
N = self.N
self.P0 = np.zeros(N)
self.P1 = np.zeros(N)
self.Q = np.zeros((N,N))
if self.dim == 2:
self.P1_mat = np.zeros((self.index_max[0]+1, self.index_max[1]+1))
self.Ps_mat = np.zeros((self.index_max[0]+1, self.index_max[1]+1))
self.P0_mat = np.zeros((self.index_max[0]+1, self.index_max[1]+1))
def set_P(self, pos_init):
# Initial distribution is a delta function at 0 or (0,0)
# Use the distribute function to spread the delta
# Reset P1
self.P1 = np.zeros(self.N)
# Reset P0
self.P0 = np.zeros(self.N)
# Find the cells to be spread and the corresponding prob mass
cell_list_distributed = self.distribute(pos=pos_init, prob_total=1)
# pos_init should be different for 1D and 2D case, see below
#if self.dim == 1:
# cell_list_distributed = self.distribute(pos=[0,], prob_total=1)
#elif self.dim ==2:
# cell_list_distributed = self.distribute(pos=[0,0], prob_total=1)
# Put the spread prob mass at the position in P0 and P1
for cell_distributed in cell_list_distributed:
indexP = self.map_labelpair_indexP[cell_distributed.label]
self.P1[indexP] = cell_distributed.prob_spread
self.P0[indexP] = cell_distributed.prob_spread
# Reset prob_spread for each cell
for cell_distributed in cell_list_distributed:
cell_distributed.prob_spread = 0
def set_P_not_spread(self, pos_init):
# Set P according to initial position
# The cell containing the initial position acquires prob of 1
# Used only for dim==2
# Reset P1
self.P1 = np.zeros(self.N)
# Reset P0
self.P0 = np.zeros(self.N)
# Find the cell that contains pos_init
cell_init = self.pos_to_cell_dim2(pos_init)
indexP = self.map_labelpair_indexP[cell_init.label]
self.P1[indexP] = 1
self.P0[indexP] = 1
def create_neurites_n(self, pars_all_neurites):
# Create neurites
# pars_all_neurites=[[pars of the first neurite],[pars of the second neurites]]
for pars in pars_all_neurites:
neurite = Neurite(pars)
self.neurites.append(neurite)
def create_pulse(self, pars):
# Create pulse
# Currently, only one pulse object is needed.
pulse = Pulse(pars)
self.pulses.append(pulse)
def cal_Q(self, num_tau_invervals):
# Calculate Q for the 2D case
dt = self.dt
dtau = dt/num_tau_invervals # dtau further divides dt. It is used to deal with uniform distribution of jumps within dt.
# Get the base pulse rate
pulse_rate_base = self.pulses[0].rate # Currently, there is only a single pulse object.
# Get the base pulse amp
pulse_amp_base = self.pulses[0].amp
pulse_amp = self.pulses[0].amp
for cell in self.cells:
# Get the starting position (the center of the current cell)
pos_start = cell.pos
# Update the state-dependent pulse rate and amp
# This is the crucial part. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# Unquote the codes to use
# The total length, used to change the rate or/and amplitude
sum_length = cell.pos[0] + cell.pos[1]
# Combination of retraction rate change and pulse rate change
mu = 0.1
pulse_rate = pulse_rate_base/(1+mu*(sum_length))
pulse_amp = pulse_amp_base
# Finite pool of proteins
mu = 0.0
pulse_rate = pulse_rate_base/(1+mu*(sum_length))
Lt_half = 10
pulse_amp = pulse_amp_base * Lt_half**2 / (Lt_half**2+(sum_length)**2)
# Change the rate
mu = 1
pulse_rate = pulse_rate_base/(1+mu*(sum_length))
pulse_amp = pulse_amp_base
# No change, base rate and amp
mu = 0.0
pulse_rate = pulse_rate_base/(1+mu*(sum_length))
pulse_amp = pulse_amp_base
# Deterministic part
# Calculate the end position
pos_end = self.cal_pos_deterministic_n(ICs=pos_start, duration=dt)
# Calculate the total prob to be spread
# Note that the equation for each neurite has a pulse term, so the total pulse rate is 2*pulse_rate
#prob_deterministic = 1 - 2 * pulse_rate * dt
prob_deterministic = 1 - pulse_rate * dt
#prob_deterministic = 1 ########Test the deterministic part!!!!! Deleted later!!!!!!
# Find the cells to be spread
cell_list_distributed = self.distribute(pos_end, prob_deterministic)
# Find the second index of Q
# This should be the index corresponding to the current cell.
# Meaning of Qij is the prob of reaching i from j.?????
index_Q_2 = self.map_labelpair_indexP[cell.label]
# Spread the probs to Q
self.distribute_Q(index_Q_2, cell_list_distributed)
# Jump part
# Assume that the pulse arrives in the middle of each dtau
# The prob mass to be spread
prob_jump = pulse_rate/self.dim * dtau # In fact, this is pulse_rate / self.dim * dt * dtau / dt
for tau in np.arange(dtau/2, dt, dtau):
# The jump in the first neurite length
amp_jump = [pulse_amp, 0]
# Calculate the final postion after dt
pos_end = self.cal_pos_jump_n(ICs=pos_start, duration=dt, t_jump=tau, amp_jump=amp_jump)
# Get the cell list where the prob mass to be spread to
cell_list_distributed = self.distribute(pos_end, prob_jump)
# Distribute the prob mass to these cells
self.distribute_Q(index_Q_2, cell_list_distributed)
# The jump in the second neurite length
amp_jump = [0, pulse_amp]
pos_end = self.cal_pos_jump_n(ICs=pos_start, duration=dt, t_jump=tau, amp_jump=amp_jump)
cell_list_distributed = self.distribute(pos_end, prob_jump)
self.distribute_Q(index_Q_2, cell_list_distributed)
def cal_Q_random(self, num_pt, num_tau_invervals):
# Calculating Q by randomly sampling trajectories
# This is used only in the 2D case
# Calculate Q for the 2D case
dt = self.dt
dtau = dt/num_tau_invervals # dtau further divides dt. It is used to deal with uniform distribution of jumps within dt.
# Get the base pulse rate
pulse_rate_base = self.pulses[0].rate # Currently, there is only a single pulse object.
# Get the base pulse amp
pulse_amp_base = self.pulses[0].amp
for cell in self.cells:
print("Working on cell", cell.label)
# Initiate cell list to spread probability mass
cell_list_distributed = []
# Sample points uniformly in the cell
pts = np.random.uniform(cell.pos_min, cell.pos_max, (num_pt, self.dim))
# Find the end positions
# calculate Qij
# Sum of the lengths
# Assume that the lengths are the same as the center positions of the cell
sum_length = cell.pos[0] + cell.pos[1]
# Update the state-dependent pulse rate and amp
# This is the crucial part. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# Unquote the codes to use
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# Finite pool of proteins
#mu = 0.0
#pulse_rate = pulse_rate_base/(1+mu*(sum_length))
#Lt_half = 10
#pulse_amp = pulse_amp_base * Lt_half**2 / (Lt_half**2+(sum_length)**2)
# Change the amp
phi = 0.5
pulse_rate = pulse_rate_base
pulse_amp = pulse_amp_base/(1+phi*(sum_length))
# Change the rate
mu = 0.5
pulse_rate = pulse_rate_base/(1+mu*(sum_length))
pulse_amp = pulse_amp_base
# No change, base rate and amp
pulse_rate = pulse_rate_base
pulse_amp = pulse_amp_base
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# Calculate the total prob to be spread for the deterministic motion
# The total pulse rate is pulse_rate
# Each neurite's pulse rate is pulse_rate/self.dim
prob_deterministic = 1 - pulse_rate * dt
prob_jump = pulse_rate/self.dim * dtau
for pos_start in pts:
####### Deterministic part
# Calculate the end position
pos_end = self.cal_pos_deterministic_n(ICs=pos_start, duration=dt)
# Find the cell where the end position lies
cell_end = self.pos_to_cell_dim2(pos_end)
# Add the prob mass to this cell
cell_end.prob_spread += prob_deterministic/num_pt
# Add this cell to the cell list, if it is not there
if not (cell_end in cell_list_distributed):
cell_list_distributed.append(cell_end)
####### Jump part
# The prob mass to be spread
# For each neurite, the pulse rate is pulse_rate/2
prob_jump = pulse_rate/2 * dtau
# Assume that the pulse arrives in the middle of each dtau
for tau in np.arange(dtau/2, dt, dtau):
# The jump in the first neurite length
amp_jump = [pulse_amp, 0]
# Calculate the final postion after dt
pos_end = self.cal_pos_jump_n(ICs=pos_start, duration=dt, t_jump=tau, amp_jump=amp_jump)
# Find the cell where the end position lies
cell_end = self.pos_to_cell_dim2(pos_end)
# Add the prob mass to this cell
cell_end.prob_spread += prob_jump/num_pt
# Add this cell to the cell list, if it is not there
if not (cell_end in cell_list_distributed):
cell_list_distributed.append(cell_end)
# The jump in the second neurite length
amp_jump = [0, pulse_amp]
pos_end = self.cal_pos_jump_n(ICs=pos_start, duration=dt, t_jump=tau, amp_jump=amp_jump)
# Find the cell where the end position lies
cell_end = self.pos_to_cell_dim2(pos_end)
# Add the prob mass to this cell
cell_end.prob_spread += prob_jump/num_pt
# Add this cell to the cell list, if it is not there
if not (cell_end in cell_list_distributed):
cell_list_distributed.append(cell_end)
# Find the second index of Q
# This should be the index corresponding to the current cell.
# Meaning of Qij is the prob of j-->i.
index_Q_2 = self.map_labelpair_indexP[cell.label]
# Spread the probs to Q
self.distribute_Q(index_Q_2, cell_list_distributed)
# Reset prob_spread for each cell in cell_list_distributed
for c in cell_list_distributed:
c.prob_spread = 0
def pos_to_cell_dim2(self, pos):
# Find the cell that contains the given position 'pos'
x, y = pos
dx, dy = self.dx
label_x = int(np.floor(x/dx))
label_y = int(np.floor(y/dy))
label = (label_x, label_y)
index = self.map_labelpair_indexP[label]
return self.cells[index]
def cal_Q_1(self, num_tau_invervals):
# Calculate Q in 1D case
dt = self.dt
dtau = dt/num_tau_invervals # dtau further divides dt. It is used to deal with uniform distribution of jumps within dt.
# Get the base pulse rate
pulse_rate_base = self.pulses[0].rate # Currently, there is only a single pulse object.
# Get the base pulse amp
pulse_amp_base = self.pulses[0].amp
pulse_amp = self.pulses[0].amp
for cell in self.cells:
# Get the starting position (the center of the current cell)
pos_start = cell.pos
# Update the state-dependent pulse rate and amp
# This is the crucial part. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
pulse_rate = pulse_rate_base/(1+0*cell.pos[0])
# Deterministic part
# Calculate the end position
pos_end = self.cal_pos_deterministic_n(ICs=pos_start, duration=dt)
# Calculate the total prob to be spread
prob_deterministic = 1 - pulse_rate * dt
# Find the cells to be spread
cell_list_distributed = self.distribute(pos_end, prob_deterministic)
# Find the second index of Q
# This should be the index corresponding to the current cell.
# Meaning of Qij is the prob of reaching i from j.?????
index_Q_2 = self.map_labelpair_indexP[cell.label]
# Spread the probs to Q
self.distribute_Q(index_Q_2, cell_list_distributed)
#if cell.label == (91,):
# print("cell_list_distributed, deterministic")
# print("Starting position", cell.pos)
# print("end position", pos_end)
# print("prob spread total", prob_deterministic)
# for cell_temp in cell_list_distributed:
# print("cell_list_distributed", cell_temp.label, "prob spread", cell_temp.prob_spread)
# print(" ")
# Jump part
# Assume that the pulse arrives in the middle of each dtau
for tau in np.arange(dtau/2, dt, dtau):
# The jump in the first neurite length
amp_jump = [pulse_amp,]
# Calculate the final postion after dt
pos_end = self.cal_pos_jump_n(ICs=pos_start, duration=dt, t_jump=tau, amp_jump=amp_jump)
# The prob mass to be spread
prob_jump = pulse_rate * dtau # In fact, this is pulse_rate*dt *dtau/dt
# Get the cell list where the prob mass to be spread to
cell_list_distributed = self.distribute(pos_end, prob_jump)
self.distribute_Q(index_Q_2, cell_list_distributed)
#if cell.label == (91,):
# print("cell_list_distributed, jump")
# print("Starting position", cell.pos)
# print("end position", pos_end)
# print("prob spread", prob_jump)
# for cell_temp in cell_list_distributed:
# print("cell_list_distributed", cell_temp.label, "prob spread", cell_temp.prob_spread)
# print(" ")
def distribute_Q(self, index_Q_2, cell_list_distributed):
# Assign the probs of cell list to entries of Q
# Note that index_Q_2 is the starting state
# index_Q_1 is the end state
for cell_distributed in cell_list_distributed:
index_Q_1 = self.map_labelpair_indexP[cell_distributed.label]
self.Q[index_Q_1][index_Q_2] += cell_distributed.prob_spread
def cal_pos_deterministic_n(self, ICs, duration):
# Calculate the position after 'duration', starting from ICs
# There is no jump within 'duration'.
sol = odeint(self.RHS_deterministic_n, y0 = ICs, t=[0, duration])
pos_end = []
for d in range(self.dim):
pos_end.append(sol[-1][d])
# If the new position is outside the region, just return the initial conditions
if self.list_compare_less_equal(pos_end, self.xmax):
return pos_end
else:
return ICs
def cal_pos_jump_n(self, ICs, duration, t_jump, amp_jump):
# Calculate the position after 'duration', starting from ICs,
# when there is a jump at t_jump
# Calculate the position before jump
sol = odeint(self.RHS_deterministic_n, y0 = ICs, t=[0, t_jump])
# Apply the jump
ICs_new = []
for d in range(self.dim):
ICs_new.append(sol[-1][d]+amp_jump[d])
# Use the position after jump as initial conditions
sol = odeint(self.RHS_deterministic_n, y0 = ICs_new, t=[t_jump, duration])
# Store the final position
pos_end = []
for d in range(self.dim):
pos_end.append(sol[-1][d])
# If the final position is outside the region, just return the initial conditions.
if self.list_compare_less_equal(pos_end, self.xmax):
return pos_end
else:
return ICs
def list_compare_less_equal(self, l1, l2):
# Compare two lists to see whether elements in the first list are less or equal to the corresponding elements in the second list.
# This is used in the functions to calculate the deterministic motion and the motion with a jump
for a1, a2 in zip(l1, l2):
if a1>a2:
return False
return True
def RHS_deterministic_n(self, y, t):
# This function gives the right-hand side of each equation
# This is a crucial function!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
RHS = []
for d in range(self.dim):
# Get the base parameter values
beta = self.neurites[d].beta
Kg = self.neurites[d].Kg
ng = self.neurites[d].ng
r = self.neurites[d].r
# For a single neurite
if self.dim==1:
deri = beta * Hill(y[d], Kg, ng) - r * y[d]
# For 2 neurites
#deri = beta * Hill(y[d], Kg, ng) - r*(1+1*y[1-d]) * y[d] # For mutual inhibition through the retraction rate
#deri = beta * Hill(y[d], Kg, ng) - r*(1+0.3*y[1-d])/(1+0.3*Hill(y[d],K=1,n=2)) * y[d]
#deri = beta * Hill(y[d], Kg, ng) - r*(1+1*Heavi(np.max([y[d], y[1-d]]), 3)*(np.max([y[d], y[1-d]])-3)) * y[d] # inhibition from itself is included
# exponential decay of inhibitory substance
#deri = beta * Hill(y[d], Kg, ng) - r*(1+0.5*(y[d]+y[1-d])*np.exp(-0.9*y[d]))* y[d]
# Simple decay of inhibitory substance
if self.dim==2:
# Crucial codes to change the rates with lengths
# Unquote to use
# No change, base rates
#deri = beta/(1+0*(y[d]+y[1-d])/(1+0*y[d])) * Hill(y[d], Kg, ng) - r*(1+0.0*(y[d]+y[1-d])/(1+0.0*y[d])) * y[d] # inhibition from itself is included
# Test the deterministic part
#deri = 0
# Change the retraction rates
#deri = beta/(1+0*(y[d]+y[1-d])/(1+0*y[d])) * Hill(y[d], Kg, ng) - r*(1+0.02*(y[d]+y[1-d])/(1+0.0*y[d])) * y[d] # inhibition from itself is included
# For test of bistability
#deri = beta/(1+0*(y[d]+y[1-d])/(1+0*y[d])) * Hill(y[d], Kg, n=1) - r*(1+2.52*(y[d]+y[1-d])/(1+3.9*y[d])) * y[d]
# Combination of the retraction rate and pulse rate
#deri = beta/(1+0*(y[d]+y[1-d])/(1+0*y[d])) * Hill(y[d], Kg, ng) - r*(1+0.5*(y[d]+y[1-d])/(1+3.5*y[d])) * y[d] # inhibition from itself is included
alpha = 0.01
deri = beta * Hill(y[d], Kg, ng) - r*(1+alpha*(y[d]+y[1-d])) * y[d] # inhibition from itself is included
RHS.append(deri)
return RHS
def distance_square_dim2(self, x, y):
# calculate the distance between x and y, which are both vectors
# This is Eucledian distance
return np.sqrt((x[0]-y[0])**2+(x[1]-y[1])**2)
def distance_square_dim1(self, x, y):
# calculate the distance between x and y
return np.abs(x-y)
def distribute(self, pos, prob_total=1):
# Distribute the probability mass ``prob_total'' to the target position ``pos''
# Create a list for the cells to be spread prob mass
list_cell_spread = []
# 2D case
if self.dim == 2:
# Extract the target positions
x, y = pos
# Cell sizes in the two directions
dx, dy = self.dx
# Maximum indices in the two direction
label_max_x, label_max_y = self.index_max
# Find the label of the nearest lattice point
# Note that this is not the same as a cell label
# The bottom-left corner has lattice label (0,0),
# while the top-right corner has (self.n_cells[0], self.n_cells[1])
nx = int(np.ceil(x/dx - 0.5))
ny = int(np.ceil(y/dy - 0.5))
# Sum of inverse distances to the nearest cells
d_inv_total = 0
# Spread (unnormalized probabilities)
for i in range(nx-1, nx+1): #i=nx-1, nx
for j in range(ny-1, ny+1): #j=ny-1, ny
if i>=0 and j>=0 and i<=label_max_x and j<=label_max_y: # check whether (i,j) is a valid cell label pair
# find the index of the cell
index_cell = self.map_labelpair_indexP[(i,j)]
# Get the cell
cell = self.cells[index_cell]
# Calculate the distance of the cell center to the target position
# This might be zero, so we add 1e-8 to it to prevent 1/0 when calculating 1/d
d = self.distance_square_dim2(cell.pos, pos) + 1e-8
d_inv_total += 1/d
# Spread the probability (unnormalized)
# The closer the distance, the higher the proportion of probability
cell.prob_spread = prob_total*1/d
# Put the cell into the list
list_cell_spread.append(cell)
# Rescale the prob to be spread
for cell in list_cell_spread:
cell.prob_spread = cell.prob_spread/d_inv_total
# 1D case
if self.dim == 1:
# Extract the target positions
x, = pos
# Cell sizes in the two directions
dx, = self.dx
# Maximum indices in the two direction
label_max_x, = self.index_max
nx = int(np.ceil(x/dx - 0.5))
# Sum of inverse distances to the nearest cells
d_inv_total = 0
# Spread (unnormalized probabilities)
for i in range(nx-1, nx+1): #i=nx-1, nx
if i>=0 and i<=label_max_x: # check whether (i,j) is a valid cell label pair
# find the index of the cell
index_cell = self.map_labelpair_indexP[(i,)]
# Get the cell
cell = self.cells[index_cell]
# Calculate the distance of the cell center to the target position
# This might be zero, so we add 1e-8 to it to prevent 1/0 when calculating 1/d
d = self.distance_square_dim1(cell.pos[0], pos[0]) + 1e-8
d_inv_total += 1/d
# Spread the probability (unnormalized)
# The closer the distance, the higher the proportion of probability
cell.prob_spread = prob_total*1/d
# Put the cell into the list
list_cell_spread.append(cell)
# Rescale the prob to be spread
for cell in list_cell_spread:
cell.prob_spread = cell.prob_spread/d_inv_total
# These codes spread the probability according to a discrete form of the delta function.
# This method introduces a large fake diffusion and is currently abandoned.
'''
# Distribute the probability mass ``prob_total'' to the target position ``pos''
# Create lists to store mim and max indices for cells to be distributed to.
index_min = []
index_max = []
# To simplify codes
dx = self.dx
xmin = self.xmin
for d in range(self.dim):
# Calculate the smallest possible index
ind_temp = int(np.ceil((pos[d]-xmin[d])/dx[d] -2 -0.5))
# Compare it with the lowest acceptale index
ind_temp = max(ind_temp, 0)
# Add the final result to the list
index_min.append( ind_temp )
# Calculate the largest possible index
ind_temp = int(np.floor((pos[d]-xmin[d])/dx[d] +2 -0.5))
# Compare it with the largest acceptale index
ind_temp = min(ind_temp, self.index_max[d])
# Add the final result to the list
index_max.append( ind_temp )
# Create a list for the cells to be spread prob mass
list_cell_spread = []
# A sum of probs for rescaling
prob_sum = 0
# spread prob mass in 2D case
if self.dim == 2:
for n1 in range(index_min[0], index_max[0] + 1):
for n2 in range(index_min[1], index_max[1] + 1):
# Get the index of a cell in self.cells
n = self.map_labelpair_indexP[(n1,n2)]
# Get the cell
cell = self.cells[n]
# Spread the probability
cell.prob_spread = delta(cell.pos[0]-pos[0], dx[0]) * delta(cell.pos[1]-pos[1], dx[1])
prob_sum += cell.prob_spread
list_cell_spread.append(cell)
# spread prob mass in 1D case
if self.dim == 1:
for n1 in range(index_min[0], index_max[0] + 1):
# Get the index of a cell in self.cells
n = self.map_labelpair_indexP[(n1,)]
# Get the cell
cell = self.cells[n]
# Spread the probability
cell.prob_spread = delta(cell.pos[0]-pos[0], dx[0])
prob_sum += cell.prob_spread
list_cell_spread.append(cell)
for cell in list_cell_spread:
# Rescale the prob to be spread
# For a target position near boundaries, only part of the support of the discrete delta is used.
# Therefore, prob_sum may not be 1
# Furthermore, the above codes do not include prob_total. They actually only spread a prob mass of 1.
cell.prob_spread = cell.prob_spread/prob_sum * prob_total
# Return the list of cells
'''
return list_cell_spread
def check_Q_sum_to_1(self, Q):
# check whether each column in the input Q is summed to 1
# The input Q can be the original self.Q, or iterated self.Q_iter and so on.
# This is used to checked whehter Q is correctly generated.
self.column_sums_Q = []
index_list = [] # Used for plotting
for n in range(self.N):
index_list.append(n)
self.column_sums_Q.append(np.sum(Q[:,n]))
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(index_list, self.column_sums_Q)
ax.set_ylabel(r'Q column sum')
ax.set_xlabel('index')
# This is used to prevent the yscale from becoming exponential, such as 1e-12+1, which is essentially 1
ax.ticklabel_format(useOffset=False)
def store_Q(self, filename):
# Write the Q matrix to a file for further Markov chain analysis in Matlab
N = self.N
# Open the file and write data
with open(filename, 'w') as f_obj:
# The first line stores the dimension of Q, i.e, N
#f_obj.write(str(self.N)+'\n')
# Write the entries of Q
# Note that by the convention of GCM, the 2nd index of Q is the starting state
# But in matlab, the 1st index is the starting state
for j in range(N):
for i in range(N):
f_obj.write(str(self.Q[i,j])+' ')
f_obj.write('\n')
def read_Q(self, filename):
# Read the stored Q
# Note that the stored Q follows the convention in math textbooks
# Qij is the prob of i-->j
# But here in GCM, Qij is the prob of j-->i
# Open the file and read data
with open(filename) as file_obj:
lines = file_obj.readlines()
# Auxillary indices
i = 0 # Current row in the file (ith line in lines_read)
j = 0 # Current column in the line (jth element in the current line)
# If the stored matrix does not match the current settng, quit.
line_test = lines[0]
line_test_str= (line_test.rstrip()).split()
if len(line_test_str) != self.N:
print("Not match, failed")
return "Failed"
for line in lines:
# First get rid of '\n' at the end of each line and split the remaining string into parts.
line_str= (line.rstrip()).split()
# Reset j
j = 0