├── Model_sim_exp1.py ├── Model_sim_exp1.py.cfg ├── Model_sim_exp2.py ├── Model_sim_exp2.py.cfg ├── Model_sim_exp3.py ├── Model_sim_exp3.py.cfg ├── README.md ├── generate_stimuli.py └── stp_ocl_implementation.py /Model_sim_exp1.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from PIL import Image 3 | import matplotlib.pyplot as plt 4 | from nengo.dists import Uniform 5 | import nengo 6 | import math 7 | from stp_ocl_implementation import * 8 | import os, inspect 9 | from nengo_extras.vision import Gabor, Mask 10 | from random import randint 11 | import nengo.spa as spa 12 | import os.path 13 | 14 | #SIMULATION CONTROL for GUI 15 | uncued=False #set if you want to run both the cued and uncued model 16 | load_gabors_svd=True #set to false if you want to generate new ones 17 | store_representations = False #store representations of model runs (for Fig 3 & 4) 18 | store_decisions = False #store decision ensemble (for Fig 5 & 6) 19 | store_spikes_and_resources = False #store spikes, calcium etc. (Fig 3 & 4) 20 | 21 | #specify here which sim you want to run if you do not use the nengo GUI 22 | #1 = simulation to generate Fig 3 & 4 23 | #2 = simulation to generate Fig 5 & 6 24 | sim_to_run = 1 25 | sim_no="1" #simulation number (used in the names of the outputfiles) 26 | 27 | 28 | #set this if you are using nengo OCL 29 | platform = cl.get_platforms()[0] #select platform, should be 0 30 | device=platform.get_devices()[1] #select GPU, use 0 (Nvidia 1) or 1 (Nvidia 3) 31 | context=cl.Context([device]) 32 | 33 | 34 | 35 | #MODEL PARAMETERS 36 | D = 24 #dimensions of representations 37 | Ns = 1000 #number of neurons in sensory layer 38 | Nm = 1500 #number of neurons in memory layer 39 | Nc = 1500 #number of neurons in comparison 40 | Nd = 1000 #number of neurons in decision 41 | 42 | 43 | #LOAD INPUT STIMULI (images created using the psychopy package) 44 | #(Stimuli should be in a subfolder named 'Stimuli') 45 | 46 | #width and height of images 47 | diameter=col=row=128 48 | 49 | #load grating stimuli 50 | angles=np.arange(-90,90,1) #rotation 51 | phases=np.arange(0,1,0.1) #phase 52 | 53 | try: 54 | imagearr = np.load('Stimuli/all_stims.npy') #load stims if previously generated 55 | except FileNotFoundError: #or generate 56 | imagearr=np.zeros((0,diameter**2)) 57 | for phase in phases: 58 | for angle in angles: 59 | name="Stimuli/stim"+str(angle)+"_"+str(round(phase,1))+".png" 60 | img=Image.open(name) 61 | img=np.array(img.convert('L')) 62 | imagearr=np.vstack((imagearr,img.ravel())) 63 | 64 | #also load the bull's eye 'impulse stimulus' 65 | name="Stimuli/stim999.png" 66 | img=Image.open(name) 67 | img=np.array(img.convert('L')) 68 | imagearr=np.vstack((imagearr,img.ravel())) 69 | 70 | #normalize to be between -1 and 1 71 | imagearr=imagearr/255 72 | imagearr=2 * imagearr - 1 73 | 74 | #imagearr is a (1801, 16384) np array containing all stimuli + the impulse 75 | np.save('Stimuli/all_stims.npy',imagearr) 76 | 77 | 78 | 79 | #INPUT FUNCTIONS 80 | 81 | #set default input 82 | memory_item_cued = 0 83 | probe_cued = 0 84 | memory_item_uncued = 0 85 | probe_uncued = 0 86 | 87 | #input stimuli 88 | #250 ms memory items | 0-250 89 | #800 ms fixation | 250-1050 90 | #20 ms reactivation | 1050-1070 91 | #1080 ms fixation | 1070-2150 92 | #100 ms impulse | 2150-2250 93 | #400 ms fixation | 2250-2650 94 | #250 ms probe | 2650-2900 95 | def input_func_cued(t): 96 | if t > 0 and t < 0.25: 97 | return imagearr[memory_item_cued,:]/100 98 | elif t > 2.15 and t < 2.25: 99 | return imagearr[-1,:]/50 #impulse, twice the contrast of other items 100 | elif t > 2.65 and t < 2.90: 101 | return imagearr[probe_cued,:]/100 102 | else: 103 | return np.zeros(128*128) #blank screen 104 | 105 | def input_func_uncued(t): 106 | if t > 0 and t < 0.25: 107 | return imagearr[memory_item_uncued,:]/100 108 | elif t > 2.15 and t < 2.25: 109 | return imagearr[-1,:]/50 #impulse, twice the contrast of other items 110 | elif t > 2.65 and t < 2.90: 111 | return imagearr[probe_uncued,:]/100 112 | else: 113 | return np.zeros(128*128) #blank screen 114 | 115 | #reactivate memory cued ensemble with nonspecific signal 116 | def reactivate_func(t): 117 | if t>1.050 and t<1.070: 118 | return np.ones(Nm)*0.0200 119 | else: 120 | return np.zeros(Nm) 121 | 122 | #Create matrix of sine and cosine values associated with the stimuli 123 | #so that we can later specify a transform from stimuli to rotation 124 | Fa = np.tile(angles,phases.size) #want to do this for each phase 125 | Frad = (Fa/90) * math.pi #make radians 126 | Sin = np.sin(Frad) 127 | Cos = np.cos(Frad) 128 | sincos = np.vstack((Sin,Cos)) #sincos 129 | 130 | #Create eval points so that we can go from sine and cosine of theta in sensory and memory layer 131 | #to the difference in theta between the two 132 | samples = 10000 133 | sinAcosA = nengo.dists.UniformHypersphere(surface=True).sample(samples,2) 134 | thetaA = np.arctan2(sinAcosA[:,0],sinAcosA[:,1]) 135 | thetaDiff = (90*np.random.random(samples)-45)/180*np.pi 136 | thetaB = thetaA + thetaDiff 137 | 138 | sinBcosB = np.vstack((np.sin(thetaB),np.cos(thetaB))) 139 | scale = np.random.random(samples)*0.9+0.1 140 | sinBcosB = sinBcosB * scale 141 | ep = np.hstack((sinAcosA,sinBcosB.T)) 142 | 143 | 144 | #continuous variant of arctan(a,b)-arctan(c,d) 145 | def arctan_func(v): 146 | yA, xA, yB, xB = v 147 | z = np.arctan2(yA, xA) - np.arctan2(yB, xB) 148 | pos_ans = [z, z+2*np.pi, z-2*np.pi] 149 | i = np.argmin(np.abs(pos_ans)) 150 | return pos_ans[i]*90/math.pi 151 | 152 | 153 | 154 | #MODEL 155 | 156 | #gabor generation for a particular model-participant 157 | def generate_gabors(): 158 | 159 | global e_cued 160 | global U_cued 161 | global compressed_im_cued 162 | 163 | global e_uncued 164 | global U_uncued 165 | global compressed_im_uncued 166 | 167 | #to speed things up, load previously generated ones 168 | if load_gabors_svd & os.path.isfile('Stimuli/gabors_svd_cued.npz'): 169 | gabors_svd_cued = np.load('Stimuli/gabors_svd_cued.npz') #load stims if previously generated 170 | e_cued = gabors_svd_cued['e_cued'] 171 | U_cued = gabors_svd_cued['U_cued'] 172 | compressed_im_cued = gabors_svd_cued['compressed_im_cued'] 173 | print("SVD cued loaded") 174 | 175 | else: #or generate and save 176 | 177 | #cued module 178 | #for each neuron in the sensory layer, generate a Gabor of 1/3 of the image size 179 | gabors_cued = Gabor().generate(Ns, (col/3, row/3)) 180 | #put gabors on image and make them the same shape as the stimuli 181 | gabors_cued = Mask((col, row)).populate(gabors_cued, flatten=True).reshape(Ns, -1) 182 | #normalize 183 | gabors_cued=gabors_cued/abs(max(np.amax(gabors_cued),abs(np.amin(gabors_cued)))) 184 | #gabors are added to imagearr for SVD 185 | x_cued=np.vstack((imagearr,gabors_cued)) 186 | 187 | #SVD 188 | print("SVD cued started...") 189 | U_cued, S_cued, V_cued = np.linalg.svd(x_cued.T) 190 | print("SVD cued done") 191 | 192 | #Use result of SVD to create encoders 193 | e_cued = np.dot(gabors_cued, U_cued[:,:D]) #encoders 194 | compressed_im_cued = np.dot(imagearr[:1800,:]/100, U_cued[:,:D]) #D-dimensional vector reps of the images 195 | compressed_im_cued = np.vstack((compressed_im_cued, np.dot(imagearr[-1,:]/50, U_cued[:,:D]))) 196 | 197 | np.savez('Stimuli/gabors_svd_cued.npz', e_cued=e_cued, U_cued=U_cued, compressed_im_cued=compressed_im_cued) 198 | 199 | #same for uncued module 200 | if uncued: 201 | 202 | if load_gabors_svd & os.path.isfile('Stimuli/gabors_svd_uncued.npz'): 203 | gabors_svd_uncued = np.load('Stimuli/gabors_svd_uncued.npz') #load stims if previously generated 204 | e_uncued = gabors_svd_uncued['e_uncued'] 205 | U_uncued = gabors_svd_uncued['U_uncued'] 206 | compressed_im_uncued = gabors_svd_uncued['compressed_im_uncued'] 207 | print("SVD uncued loaded") 208 | else: 209 | gabors_uncued = Gabor().generate(Ns, (col/3, row/3))#.reshape(N, -1) 210 | gabors_uncued = Mask((col, row)).populate(gabors_uncued, flatten=True).reshape(Ns, -1) 211 | gabors_uncued=gabors_uncued/abs(max(np.amax(gabors_uncued),abs(np.amin(gabors_uncued)))) 212 | x_uncued=np.vstack((imagearr,gabors_uncued)) 213 | 214 | print("SVD uncued started...") 215 | U_uncued, S_uncued, V_uncued = np.linalg.svd(x_uncued.T) 216 | print("SVD uncued done") 217 | e_uncued = np.dot(gabors_uncued, U_uncued[:,:D]) 218 | compressed_im_uncued=np.dot(imagearr[:1800,:]/100, U_uncued[:,:D]) 219 | compressed_im_uncued = np.vstack((compressed_im_uncued, np.dot(imagearr[-1,:]/50, U_uncued[:,:D]))) 220 | 221 | np.savez('Stimuli/gabors_svd_uncued.npz', e_uncued=e_uncued, U_uncued=U_uncued, compressed_im_uncued=compressed_im_uncued) 222 | 223 | 224 | nengo_gui_on = __name__ == 'builtins' #python3 225 | 226 | def create_model(seed=None): 227 | 228 | global model 229 | 230 | #create vocabulary to show representations in gui 231 | if nengo_gui_on: 232 | vocab_angles = spa.Vocabulary(D) 233 | for name in [0, 3, 7, 12, 18, 25, 33, 42]: 234 | #vocab_angles.add('D' + str(name), np.linalg.norm(compressed_im_cued[name+90])) #take mean across phases 235 | v = compressed_im_cued[name+90] 236 | nrm = np.linalg.norm(v) 237 | if nrm > 0: 238 | v /= nrm 239 | vocab_angles.add('D' + str(name), v) #take mean across phases 240 | 241 | v = np.dot(imagearr[-1,:]/50, U_cued[:,:D]) 242 | nrm = np.linalg.norm(v) 243 | if nrm > 0: 244 | v /= nrm 245 | vocab_angles.add('Impulse', v) 246 | 247 | #model = nengo.Network(seed=seed) 248 | model = spa.SPA(seed=seed) 249 | with model: 250 | 251 | #input nodes 252 | inputNode_cued=nengo.Node(input_func_cued,label='input_cued') 253 | reactivate=nengo.Node(reactivate_func,label='reactivate') 254 | 255 | #sensory ensemble 256 | sensory_cued = nengo.Ensemble(Ns, D, encoders=e_cued, intercepts=Uniform(0.01, .1),radius=1,label='sensory_cued') 257 | nengo.Connection(inputNode_cued,sensory_cued,transform=U_cued[:,:D].T) 258 | 259 | #memory ensemble 260 | memory_cued = nengo.Ensemble(Nm, D,neuron_type=stpLIF(), intercepts=Uniform(0.01, .1),radius=1,label='memory_cued') 261 | nengo.Connection(reactivate,memory_cued.neurons) #potential reactivation 262 | nengo.Connection(sensory_cued, memory_cued, transform=.1) #.1) 263 | 264 | #recurrent STSP connection 265 | nengo.Connection(memory_cued, memory_cued,transform=1, learning_rule_type=STP(), solver=nengo.solvers.LstsqL2(weights=True)) 266 | 267 | #comparison represents sin, cosine of theta of both sensory and memory ensemble 268 | comparison_cued = nengo.Ensemble(Nc, dimensions=4,radius=math.sqrt(2),intercepts=Uniform(.01, 1),label='comparison_cued') 269 | nengo.Connection(sensory_cued, comparison_cued[:2],eval_points=compressed_im_cued[0:-1],function=sincos.T) 270 | nengo.Connection(memory_cued, comparison_cued[2:],eval_points=compressed_im_cued[0:-1],function=sincos.T) 271 | 272 | #decision represents the difference in theta decoded from the sensory and memory ensembles 273 | decision_cued = nengo.Ensemble(n_neurons=Nd, dimensions=1,radius=45,label='decision_cued') 274 | nengo.Connection(comparison_cued, decision_cued, eval_points=ep, scale_eval_points=False, function=arctan_func) 275 | 276 | #same for uncued 277 | if uncued: 278 | inputNode_uncued=nengo.Node(input_func_uncued,label='input_uncued') 279 | 280 | sensory_uncued = nengo.Ensemble(Ns, D, encoders=e_uncued, intercepts=Uniform(0.01, .1),radius=1,label='sensory_uncued') 281 | nengo.Connection(inputNode_uncued,sensory_uncued,transform=U_uncued[:,:D].T) 282 | 283 | memory_uncued = nengo.Ensemble(Nm, D,neuron_type=stpLIF(), intercepts=Uniform(0.01, .1),radius=1,label='memory_uncued') 284 | nengo.Connection(sensory_uncued, memory_uncued, transform=.1) 285 | 286 | nengo.Connection(memory_uncued, memory_uncued,transform=1,learning_rule_type=STP(),solver=nengo.solvers.LstsqL2(weights=True)) 287 | 288 | comparison_uncued = nengo.Ensemble(Nd, dimensions=4,radius=math.sqrt(2),intercepts=Uniform(.01, 1),label='comparison_uncued') 289 | 290 | nengo.Connection(memory_uncued, comparison_uncued[2:],eval_points=compressed_im_uncued[0:-1],function=sincos.T) 291 | nengo.Connection(sensory_uncued, comparison_uncued[:2],eval_points=compressed_im_uncued[0:-1],function=sincos.T) 292 | 293 | decision_uncued = nengo.Ensemble(n_neurons=Nd, dimensions=1,radius=45,label='decision_uncued') 294 | nengo.Connection(comparison_uncued, decision_uncued, eval_points=ep, scale_eval_points=False, function=arctan_func) 295 | 296 | #decode for gui 297 | if nengo_gui_on: 298 | model.sensory_decode = spa.State(D, vocab=vocab_angles, subdimensions=12, label='sensory_decode') 299 | for ens in model.sensory_decode.all_ensembles: 300 | ens.neuron_type = nengo.Direct() 301 | nengo.Connection(sensory_cued, model.sensory_decode.input,synapse=None) 302 | 303 | model.memory_decode = spa.State(D, vocab=vocab_angles, subdimensions=12, label='memory_decode') 304 | for ens in model.memory_decode.all_ensembles: 305 | ens.neuron_type = nengo.Direct() 306 | nengo.Connection(memory_cued, model.memory_decode.input,synapse=None) 307 | 308 | #probes 309 | if not(nengo_gui_on): 310 | if store_representations: #sim 1 trials 1-100 311 | #p_dtheta_cued=nengo.Probe(decision_cued, synapse=0.01) 312 | model.p_mem_cued=nengo.Probe(memory_cued, synapse=0.01) 313 | #p_sen_cued=nengo.Probe(sensory_cued, synapse=0.01) 314 | 315 | if uncued: 316 | model.p_mem_uncued=nengo.Probe(memory_uncued, synapse=0.01) 317 | 318 | if store_spikes_and_resources: #sim 1 trial 1 319 | model.p_spikes_mem_cued=nengo.Probe(memory_cued.neurons, 'spikes') 320 | model.p_res_cued=nengo.Probe(memory_cued.neurons, 'resources') 321 | model.p_cal_cued=nengo.Probe(memory_cued.neurons, 'calcium') 322 | 323 | if uncued: 324 | model.p_spikes_mem_uncued=nengo.Probe(memory_uncued.neurons, 'spikes') 325 | model.p_res_uncued=nengo.Probe(memory_uncued.neurons, 'resources') 326 | model.p_cal_uncued=nengo.Probe(memory_uncued.neurons, 'calcium') 327 | 328 | if store_decisions: #sim 2 329 | model.p_dec_cued=nengo.Probe(decision_cued, synapse=0.01) 330 | 331 | #PLOTTING CODE 332 | from nengo.utils.matplotlib import rasterplot 333 | from matplotlib import style 334 | from plotnine import * 335 | theme = theme_classic() 336 | plt.style.use('default') 337 | 338 | def plot_sim_1(sp_c,sp_u,res_c,res_u,cal_c,cal_u, mem_cued, mem_uncued): 339 | 340 | #FIGURE 31 341 | with plt.rc_context(): 342 | plt.rcParams.update(theme.rcParams) 343 | 344 | fig, axes, = plt.subplots(2,2,squeeze=True) 345 | theme.setup_figure(fig) 346 | t = sim.trange() 347 | plt.subplots_adjust(wspace=0.05, hspace=0.05) 348 | 349 | #spikes, calcium, resources Cued 350 | ax1=axes[0,0] 351 | ax1.set_title("Cued Module") 352 | ax1.set_ylabel('# cell', color='black') 353 | ax1.set_yticks(np.arange(0,Nm,500)) 354 | ax1.tick_params('y')#, colors='black') 355 | rasterplot(sim.trange(), sp_c,ax1,colors=['black']*sp_c.shape[0]) 356 | ax1.set_xticklabels([]) 357 | ax1.set_xticks([]) 358 | ax1.set_xlim(0,3) 359 | ax2 = ax1.twinx() 360 | ax2.plot(t, res_c, "#00bfc4",linewidth=2) 361 | ax2.plot(t, cal_c, "#e38900",linewidth=2) 362 | ax2.set_yticklabels([]) 363 | ax2.set_yticks([]) 364 | ax2.set_ylim(0,1.1) 365 | 366 | #spikes, calcium, resources Uncued 367 | ax3=axes[0,1] 368 | ax3.set_title("Uncued Module") 369 | rasterplot(sim.trange(), sp_u,ax3,colors=['black']*sp_u.shape[0]) 370 | ax3.set_xticklabels([]) 371 | ax3.set_xticks([]) 372 | ax3.set_yticklabels([]) 373 | ax3.set_yticks([]) 374 | ax3.set_xlim(0,3) 375 | ax4 = ax3.twinx() 376 | ax4.plot(t, res_u, "#00bfc4",linewidth=2) 377 | ax4.plot(t, cal_u, "#e38900",linewidth=2) 378 | ax4.set_ylabel('synaptic variables', color="black",size=11) 379 | ax4.tick_params('y', labelcolor='#333333',labelsize=9,color='#333333') 380 | ax4.set_ylim(0,1.1) 381 | 382 | #representations cued 383 | plot_mc=axes[1,0] 384 | plot_mc.plot(sim.trange(),(mem_cued)); 385 | 386 | plot_mc.set_ylabel("Cosine similarity") 387 | plot_mc.set_xticks(np.arange(0.0,3.45,0.5)) 388 | plot_mc.set_xticklabels(np.arange(0,3500,500).tolist()) 389 | plot_mc.set_xlabel('time (ms)') 390 | plot_mc.set_xlim(0,3) 391 | colors=["#00c094","#00bfc4","#00b6eb","#06a4ff","#a58aff","#df70f8","#fb61d7","#ff66a8", "#c49a00"] 392 | for i,j in enumerate(plot_mc.lines): 393 | j.set_color(colors[i]) 394 | 395 | #representations uncued 396 | plot_mu=axes[1,1] 397 | 398 | plot_mu.plot(sim.trange(),(mem_uncued)); 399 | plot_mu.set_xticks(np.arange(0.0,3.45,0.5)) 400 | plot_mu.set_xticklabels(np.arange(0,3500,500).tolist()) 401 | plot_mu.set_xlabel('time (ms)') 402 | plot_mu.set_yticks([]) 403 | plot_mu.set_yticklabels([]) 404 | plot_mu.set_xlim(0,3) 405 | for i,j in enumerate(plot_mu.lines): 406 | j.set_color(colors[i]) 407 | plot_mu.legend(["0°","3°","7°","12°","18°","25°","33°","42°", "Impulse"], title="Stimulus", bbox_to_anchor=(1.02, -0.25, .30, 0.8), loc=3, 408 | ncol=1, mode="expand", borderaxespad=0.) 409 | 410 | 411 | fig.set_size_inches(11, 5) 412 | theme.apply(plt.gcf().axes[0]) 413 | theme.apply(plt.gcf().axes[1]) 414 | theme.apply(plt.gcf().axes[2]) 415 | theme.apply(plt.gcf().axes[3]) 416 | plt.savefig('Figure_3.eps', format='eps', dpi=1000) 417 | plt.show() 418 | 419 | 420 | #FIGURE 32 421 | with plt.rc_context(): 422 | plt.rcParams.update(theme.rcParams) 423 | 424 | fig, axes, = plt.subplots(1,2,squeeze=True) 425 | theme.setup_figure(fig) 426 | t = sim.trange() 427 | plt.subplots_adjust(wspace=0.1, hspace=0.05) 428 | 429 | plot_mc=axes[0] 430 | plot_mc.set_title("Cued Module") 431 | plot_mc.plot(sim.trange(),(mem_cued)); 432 | plot_mc.set_ylabel("Cosine similarity") 433 | plot_mc.set_xticks(np.arange(2.15,2.35,0.05)) 434 | plot_mc.set_xticklabels(np.arange(0,250,50).tolist()) 435 | plot_mc.set_xlabel('time after onset impulse (ms)') 436 | plot_mc.set_xlim(2.15,2.3) 437 | plot_mc.set_ylim(0,0.9) 438 | colors=["#00c094","#00bfc4","#00b6eb","#06a4ff","#a58aff","#df70f8","#fb61d7","#ff66a8", "#c49a00"] 439 | for i,j in enumerate(plot_mc.lines): 440 | j.set_color(colors[i]) 441 | 442 | plot_mu=axes[1] 443 | plot_mu.set_title("Uncued Module") 444 | plot_mu.plot(sim.trange(),(mem_uncued)); 445 | plot_mu.set_xticks(np.arange(2.15,2.35,0.05)) 446 | plot_mu.set_xticklabels(np.arange(0,250,50).tolist()) 447 | plot_mu.set_xlabel('time after onset impulse (ms)') 448 | plot_mu.set_yticks([]) 449 | plot_mu.set_yticklabels([]) 450 | plot_mu.set_xlim(2.15,2.30) 451 | plot_mu.set_ylim(0,0.9) 452 | for i,j in enumerate(plot_mu.lines): 453 | j.set_color(colors[i]) 454 | plot_mu.legend(["0°","3°","7°","12°","18°","25°","33°","42°", "Impulse"], title="Stimulus", bbox_to_anchor=(0.85, 0.25, .55, 0.8), loc=3, 455 | ncol=1, mode="expand", borderaxespad=0.) 456 | 457 | fig.set_size_inches(6, 4) 458 | 459 | theme.apply(plt.gcf().axes[0]) 460 | theme.apply(plt.gcf().axes[1]) 461 | plt.savefig('Figure_4.eps', format='eps', dpi=1000) 462 | plt.show() 463 | 464 | 465 | 466 | 467 | #SIMULATION 468 | #note that this is split for running a single trial in the nengo gui, and a full simulation 469 | 470 | 471 | #normalise stimuli to be between 0 and 180 degrees orientation 472 | def norm_p(p): 473 | if p<0: 474 | return 180+p 475 | if p>180: 476 | return p-180 477 | else: 478 | return p 479 | 480 | #Calculate normalised cosine similarity and avoid divide by 0 errors 481 | def cosine_sim(a,b): 482 | out=np.zeros(a.shape[0]) 483 | for i in range(0, a.shape[0]): 484 | if abs(np.linalg.norm(a[i])) > 0.05: 485 | out[i]=np.dot(a[i], b)/(np.linalg.norm(a[i])*np.linalg.norm(b)) 486 | return out 487 | 488 | 489 | 490 | if nengo_gui_on: 491 | generate_gabors() #generate gabors 492 | create_model(seed=0) #build model 493 | 494 | memory_item_cued = 0 + 90 495 | probe_cued = 42 + 90 496 | memory_item_uncued = 0 + 90 497 | probe_uncued = 42 + 90 498 | 499 | 500 | else: #no gui 501 | 502 | #path 503 | cur_path = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))+'/data/' #store output in data subfolder 504 | 505 | #simulation 1: recreate fig 3 & 4, 100 trials for both cued and uncued with 0 and 42 degree memory items and probes 506 | if sim_to_run == 1: 507 | 508 | print('Running simulation 1') 509 | print('') 510 | 511 | load_gabors_svd = False #no need to randomize this 512 | 513 | ntrials = 100 514 | store_representations = True 515 | store_decisions = False 516 | uncued = True 517 | 518 | 519 | #store results 520 | templates=np.array([90,93,97,102,108,115,123,132]) 521 | mem_cued = np.zeros((3000,len(templates)+1)) #keep cosine sim for 9 items (templates + impulse) 522 | mem_uncued = np.zeros((3000,len(templates)+1)) 523 | 524 | #first, run 100 trials to get average cosine sim 525 | for run in range(ntrials): 526 | 527 | print('Run ' + str(run+1)) 528 | 529 | #stimuli 530 | phase = 180*randint(0, 9) 531 | memory_item_cued = 0 + 90 + phase 532 | probe_cued = 42 + 90 + phase 533 | memory_item_uncued = memory_item_cued 534 | probe_uncued = probe_cued 535 | 536 | #create new gabor filters every 10 trials 537 | if run % 10 == 0: 538 | generate_gabors() 539 | 540 | create_model(seed=run) 541 | sim = StpOCLsimulator(network=model, seed=run, context=context,progress_bar=False) 542 | 543 | #run simulation 544 | sim.run(3) 545 | 546 | #reset simulator, clean probes thoroughly 547 | #print(sim.data[model.p_mem_cued].shape) 548 | #calc cosine sim with templates 549 | temp_phase = list(templates + phase) + [1800] 550 | 551 | for cnt, templ in enumerate(temp_phase): 552 | mem_cued[:,cnt] += cosine_sim(sim.data[model.p_mem_cued][:,:,],compressed_im_cued[templ,:]) 553 | mem_uncued[:,cnt] += cosine_sim(sim.data[model.p_mem_uncued][:,:,],compressed_im_uncued[templ,:]) 554 | 555 | sim.reset() 556 | for probe2 in sim.model.probes: 557 | del sim._probe_outputs[probe2][:] 558 | del sim.data 559 | sim.data = nengo.simulator.ProbeDict(sim._probe_outputs) 560 | 561 | 562 | #average 563 | mem_cued /= ntrials 564 | mem_uncued /= ntrials 565 | 566 | #second, run 1 trial to get calcium and spikes 567 | store_spikes_and_resources = True 568 | store_representations = False 569 | create_model(seed=0) #recreate model to change probes 570 | sim = StpOCLsimulator(network=model, seed=0, context=context,progress_bar=False) 571 | 572 | print('Run ' + str(ntrials+1)) 573 | sim.run(3) 574 | 575 | #store spikes and calcium 576 | sp_c = sim.data[model.p_spikes_mem_cued] 577 | res_c=np.mean(sim.data[model.p_res_cued][:,:,],1) #take mean over neurons 578 | cal_c=np.mean(sim.data[model.p_cal_cued][:,:,],1) #take mean over neurons 579 | 580 | sp_u=sim.data[model.p_spikes_mem_uncued] 581 | res_u=np.mean(sim.data[model.p_res_uncued][:,:,],1) 582 | cal_u=np.mean(sim.data[model.p_cal_uncued][:,:,],1) 583 | 584 | #plot 585 | plot_sim_1(sp_c,sp_u,res_c,res_u,cal_c,cal_u, mem_cued, mem_uncued) 586 | 587 | 588 | #simulation 2: collect data for fig 5 & 6. 1344 trials for 30 subjects 589 | if sim_to_run == 2: 590 | 591 | load_gabors_svd = False #set to false for real simulation 592 | 593 | n_subj = 30 594 | trials_per_subj = 1344 595 | store_representations = False 596 | store_decisions = True 597 | uncued = False 598 | 599 | #np array to keep track of the input during the simulation runs 600 | initialangle_c = np.zeros(n_subj*trials_per_subj) #cued 601 | angle_index=0 602 | 603 | #orientation differences between probe and memory item for each run 604 | probelist=[-42, -33, -25, -18, -12, -7, -3, 3, 7, 12, 18, 25, 33, 42] 605 | 606 | for subj in range(n_subj): 607 | 608 | #create new gabor filters and model for each new participant 609 | generate_gabors() 610 | create_model(seed=subj) 611 | 612 | #use StpOCLsimulator to make use of the Nengo OCL implementation of STSP 613 | sim = StpOCLsimulator(network=model, seed=subj, context=context,progress_bar=False) 614 | 615 | #trials come in sets of 14, which we call a run (all possible orientation differences between memory and probe), 616 | runs = int(trials_per_subj / 14) 617 | 618 | for run in range(runs): 619 | 620 | #run a trial with each possible orientation difference 621 | for cnt_in_run, anglediff in enumerate(probelist): 622 | 623 | print('Subject ' + str(subj+1) + '/' + str(n_subj) + '; Trial ' + str(run*14 + cnt_in_run + 1) + '/' + str(trials_per_subj)) 624 | 625 | #set probe and stim 626 | memory_item_cued=randint(0, 179) #random memory 627 | probe_cued=memory_item_cued+anglediff #probe based on that 628 | probe_cued=norm_p(probe_cued) #normalise probe 629 | 630 | #random phase 631 | or_memory_item_cued=memory_item_cued #original 632 | memory_item_cued=memory_item_cued+(180*randint(0, 9)) 633 | probe_cued=probe_cued+(180*randint(0, 9)) 634 | 635 | #store orientation 636 | initialangle_c[angle_index]=or_memory_item_cued 637 | 638 | #run simulation 639 | sim.run(3) 640 | 641 | #store output 642 | np.savetxt(cur_path+sim_no+"_Diff_Theta_%i_subj_%i_trial_%i.csv" % (anglediff, subj+1, run*14+cnt_in_run+1), sim.data[model.p_dec_cued][2500:2999,:], delimiter=",") 643 | 644 | #reset simulator, clean probes thoroughly 645 | sim.reset() 646 | for probe2 in sim.model.probes: 647 | del sim._probe_outputs[probe2][:] 648 | del sim.data 649 | sim.data = nengo.simulator.ProbeDict(sim._probe_outputs) 650 | 651 | angle_index=angle_index+1 652 | 653 | np.savetxt(cur_path+sim_no+"_initial_angles_cued.csv", initialangle_c,delimiter=",") 654 | -------------------------------------------------------------------------------- /Model_sim_exp1.py.cfg: -------------------------------------------------------------------------------- 1 | _viz_0 = nengo_gui.components.SpaSimilarity(model.sensory_decode,args='default') 2 | _viz_config[_viz_0].max_value = 1 3 | _viz_config[_viz_0].min_value = -0.2 4 | _viz_config[_viz_0].show_pairs = False 5 | _viz_config[_viz_0].x = 0.04565717892260996 6 | _viz_config[_viz_0].y = 0.16705865099259368 7 | _viz_config[_viz_0].width = 0.07935918403792248 8 | _viz_config[_viz_0].height = 0.13926825209046173 9 | _viz_config[_viz_0].label_visible = True 10 | _viz_1 = nengo_gui.components.Raster(model.ensembles[1]) 11 | _viz_config[_viz_1].n_neurons = 1500 12 | _viz_config[_viz_1].x = 0.06656019285839798 13 | _viz_config[_viz_1].y = 1.150196449227392 14 | _viz_config[_viz_1].width = 0.07935918403792248 15 | _viz_config[_viz_1].height = 0.13926825209046173 16 | _viz_config[_viz_1].label_visible = True 17 | _viz_3 = nengo_gui.components.Value(model.ensembles[3]) 18 | _viz_config[_viz_3].max_value = 42 19 | _viz_config[_viz_3].min_value = -42 20 | _viz_config[_viz_3].show_legend = False 21 | _viz_config[_viz_3].legend_labels = ['label_0'] 22 | _viz_config[_viz_3].synapse = 0.01 23 | _viz_config[_viz_3].x = 0.9431318210358312 24 | _viz_config[_viz_3].y = 0.17436602452359595 25 | _viz_config[_viz_3].width = 0.1216628990759502 26 | _viz_config[_viz_3].height = 0.19412416693242154 27 | _viz_config[_viz_3].label_visible = True 28 | _viz_5 = nengo_gui.components.SpaSimilarity(model.memory_decode,args='default') 29 | _viz_config[_viz_5].max_value = 1 30 | _viz_config[_viz_5].min_value = -0.2 31 | _viz_config[_viz_5].show_pairs = False 32 | _viz_config[_viz_5].x = 0.6452121695402744 33 | _viz_config[_viz_5].y = 0.9346934126330544 34 | _viz_config[_viz_5].width = 0.07935918403792248 35 | _viz_config[_viz_5].height = 0.13926825209046173 36 | _viz_config[_viz_5].label_visible = True 37 | _viz_net_graph = nengo_gui.components.NetGraph() 38 | _viz_progress = nengo_gui.components.Progress() 39 | _viz_config[_viz_progress].x = 0 40 | _viz_config[_viz_progress].y = 0 41 | _viz_config[_viz_progress].width = 100 42 | _viz_config[_viz_progress].height = 100 43 | _viz_config[_viz_progress].label_visible = True 44 | _viz_sim_control = nengo_gui.components.SimControl() 45 | _viz_config[_viz_sim_control].shown_time = 0.5 46 | _viz_config[_viz_sim_control].kept_time = 4.0 47 | _viz_config[model].pos=(0.215376711776156, 0.11438011215863245) 48 | _viz_config[model].size=(0.6131842341444002, 0.6131842341444002) 49 | _viz_config[model].expanded=True 50 | _viz_config[model].has_layout=True 51 | _viz_config[model.ensembles[0]].pos=(0.33041123391021066, 0.48315238372474056) 52 | _viz_config[model.ensembles[0]].size=(0.053763440860215055, 0.1020408163265306) 53 | _viz_config[model.ensembles[1]].pos=(0.4818870288654669, 0.8044761538057824) 54 | _viz_config[model.ensembles[1]].size=(0.053763440860215055, 0.1020408163265306) 55 | _viz_config[model.ensembles[2]].pos=(0.6054048905297307, 0.485000237818829) 56 | _viz_config[model.ensembles[2]].size=(0.053763440860215055, 0.1020408163265306) 57 | _viz_config[model.ensembles[3]].pos=(0.8321523209122026, 0.48510631596044274) 58 | _viz_config[model.ensembles[3]].size=(0.053763440860215055, 0.1020408163265306) 59 | _viz_config[model.memory_decode].pos=(0.48406219827730346, 1.1842807378985376) 60 | _viz_config[model.memory_decode].size=(0.07199178797724101, 0.11543568040366908) 61 | _viz_config[model.memory_decode].expanded=False 62 | _viz_config[model.memory_decode].has_layout=False 63 | _viz_config[model.memory_decode.state_ensembles].expanded=False 64 | _viz_config[model.memory_decode.state_ensembles].has_layout=False 65 | _viz_config[model.nodes[0]].pos=(0.06961257909962328, 0.48110559131271463) 66 | _viz_config[model.nodes[0]].size=(0.043010752688172046, 0.08163265306122448) 67 | _viz_config[model.nodes[1]].pos=(0.06989247311827958, 0.7959183673469387) 68 | _viz_config[model.nodes[1]].size=(0.043010752688172046, 0.08163265306122448) 69 | _viz_config[model.sensory_decode].pos=(0.33258900053375823, 0.06822915683220981) 70 | _viz_config[model.sensory_decode].size=(0.07711293532218821, 0.08724346412454488) 71 | _viz_config[model.sensory_decode].expanded=False 72 | _viz_config[model.sensory_decode].has_layout=False 73 | _viz_config[model.sensory_decode.state_ensembles].expanded=False 74 | _viz_config[model.sensory_decode.state_ensembles].has_layout=False -------------------------------------------------------------------------------- /Model_sim_exp2.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from PIL import Image 3 | import matplotlib.pyplot as plt 4 | from nengo.dists import Uniform 5 | import nengo 6 | import math 7 | from stp_ocl_implementation import * 8 | import os, inspect 9 | from nengo_extras.vision import Gabor, Mask 10 | from random import randint 11 | import nengo.spa as spa 12 | import os.path 13 | 14 | #SIMULATION CONTROL for GUI 15 | load_gabors_svd=True #set to false if you want to generate new ones 16 | store_representations = False #store representations of model runs (for Fig 3 & 4) 17 | store_decisions = False #store decision ensemble (for Fig 5 & 6) 18 | store_spikes_and_resources = False #store spikes, calcium etc. (Fig 3 & 4) 19 | 20 | #specify here which sim you want to run if you do not use the nengo GUI 21 | #1 = representations & spikes 22 | #2 = performance, decision signal 23 | sim_to_run = 1 24 | sim_no="1" #simulation number (used in the names of the outputfiles) 25 | 26 | #set this if you are using nengo OCL 27 | platform = cl.get_platforms()[0] #select platform, should be 0 28 | device=platform.get_devices()[0] #select GPU, use 0 (Nvidia 1) or 1 (Nvidia 3) 29 | context=cl.Context([device]) 30 | 31 | 32 | #MODEL PARAMETERS 33 | D = 24 #dimensions of representations 34 | Ns = 1000 #number of neurons in sensory layer 35 | Nm = 1500 #number of neurons in memory layer 36 | Nc = 1500 #number of neurons in comparison 37 | Nd = 1000 #number of neurons in decision 38 | 39 | 40 | #LOAD INPUT STIMULI (images created using the psychopy package) 41 | #(Stimuli should be in a subfolder named 'Stimuli') 42 | 43 | #width and height of images 44 | diameter=col=row=128 45 | 46 | #load grating stimuli 47 | angles=np.arange(-90,90,1) #rotation 48 | phases=np.arange(0,1,0.1) #phase 49 | 50 | 51 | #stim2003: 60% grey 52 | try: 53 | imagearr = np.load('Stimuli/all_stims_exp2.npy') #load stims if previously generated 54 | except FileNotFoundError: #or generate 55 | imagearr=np.zeros((0,diameter**2)) 56 | for phase in phases: 57 | for angle in angles: 58 | name="Stimuli/stim"+str(angle)+"_"+str(round(phase,1))+".png" 59 | img=Image.open(name) 60 | img=np.array(img.convert('L')) 61 | imagearr=np.vstack((imagearr,img.ravel())) 62 | 63 | name="Stimuli/stim2003.png" 64 | img=Image.open(name) 65 | img=np.array(img.convert('L')) 66 | imagearr=np.vstack((imagearr,img.ravel())) 67 | 68 | #normalize to be between -1 and 1 69 | imagearr=imagearr/255 70 | imagearr=2 * imagearr - 1 71 | 72 | #imagearr is a (1801, 16384) np array containing all stimuli + the impulse 73 | np.save('Stimuli/all_stims_exp2.npy',imagearr) 74 | 75 | 76 | 77 | #INPUT FUNCTIONS 78 | 79 | #set default input 80 | memory_item_first = 0 81 | probe_first = 0 82 | memory_item_second = 0 83 | probe_second = 0 84 | 85 | #input stimuli 1 86 | #250 ms memory items | 0-250 87 | #950 ms fixation | 250-1200 88 | #100 ms impulse | 1200-1300 89 | #500 ms fixation | 1300-1800 90 | #250 ms probe | 1800-2050 91 | #1750 fixation | 2050-3800 92 | #100 ms impulse | 3800-3900 93 | #400 ms fixation | 3900-4300 94 | #250 ms probe | 4300-4550 95 | def input_func_first(t): 96 | if t > 0 and t < 0.25: 97 | return (imagearr[memory_item_first,:]/100) * 1.0 98 | elif t > 1.2 and t < 1.3: 99 | return imagearr[-1,:]/50 #impulse, twice the contrast of other items 100 | elif t > 1.8 and t < 2.05: 101 | return imagearr[probe_first,:]/100 102 | elif t > 3.8 and t < 3.9: 103 | return imagearr[-1,:]/50 #impulse, twice the contrast of other items 104 | #elif t > 4.3 and t < 4.55: 105 | # return imagearr[probe_second,:]/100 106 | else: 107 | return np.zeros(128*128) #blank screen 108 | 109 | def input_func_second(t): 110 | if t > 0 and t < 0.25: 111 | return (imagearr[memory_item_second,:]/100) * .9 #slightly lower input for secondary item 112 | elif t > 1.2 and t < 1.3: 113 | return imagearr[-1,:]/50 #impulse, twice the contrast of other items 114 | #elif t > 1.8 and t < 2.05: 115 | # return imagearr[probe_first,:]/100 116 | elif t > 3.8 and t < 3.9: 117 | return imagearr[-1,:]/50 #impulse, twice the contrast of other items 118 | elif t > 4.3 and t < 4.55: 119 | return imagearr[probe_second,:]/100 120 | else: 121 | return np.zeros(128*128) #blank screen 122 | 123 | #reactivate second ensemble based on lateralization 124 | def reactivate_func(t): 125 | if t>2.250 and t<2.270: 126 | return np.ones(Nm)*0.0200 127 | else: 128 | return np.zeros(Nm) 129 | 130 | #Create matrix of sine and cosine values associated with the stimuli 131 | #so that we can later specify a transform from stimuli to rotation 132 | Fa = np.tile(angles,phases.size) #want to do this for each phase 133 | Frad = (Fa/90) * math.pi #make radians 134 | Sin = np.sin(Frad) 135 | Cos = np.cos(Frad) 136 | sincos = np.vstack((Sin,Cos)) #sincos 137 | 138 | #Create eval points so that we can go from sine and cosine of theta in sensory and memory layer 139 | #to the difference in theta between the two 140 | samples = 10000 141 | sinAcosA = nengo.dists.UniformHypersphere(surface=True).sample(samples,2) 142 | thetaA = np.arctan2(sinAcosA[:,0],sinAcosA[:,1]) 143 | thetaDiff = (90*np.random.random(samples)-45)/180*np.pi 144 | thetaB = thetaA + thetaDiff 145 | 146 | sinBcosB = np.vstack((np.sin(thetaB),np.cos(thetaB))) 147 | scale = np.random.random(samples)*0.9+0.1 148 | sinBcosB = sinBcosB * scale 149 | ep = np.hstack((sinAcosA,sinBcosB.T)) 150 | 151 | 152 | #continuous variant of arctan(a,b)-arctan(c,d) 153 | def arctan_func(v): 154 | yA, xA, yB, xB = v 155 | z = np.arctan2(yA, xA) - np.arctan2(yB, xB) 156 | pos_ans = [z, z+2*np.pi, z-2*np.pi] 157 | i = np.argmin(np.abs(pos_ans)) 158 | return pos_ans[i]*90/math.pi 159 | 160 | 161 | 162 | #MODEL 163 | 164 | #gabor generation for a particular model-participant 165 | def generate_gabors(): 166 | 167 | global e_first 168 | global U_first 169 | global compressed_im_first 170 | 171 | global e_second 172 | global U_second 173 | global compressed_im_second 174 | 175 | #to speed things up, load previously generated ones 176 | if load_gabors_svd & os.path.isfile('Stimuli/gabors_svd_first_exp2.npz'): 177 | gabors_svd_first = np.load('Stimuli/gabors_svd_first_exp2.npz') #load stims if previously generated 178 | e_first = gabors_svd_first['e_first'] 179 | U_first = gabors_svd_first['U_first'] 180 | compressed_im_first = gabors_svd_first['compressed_im_first'] 181 | print("SVD first loaded") 182 | 183 | else: #or generate and save 184 | 185 | #cued module 186 | #for each neuron in the sensory layer, generate a Gabor of 1/3 of the image size 187 | gabors_first = Gabor().generate(Ns, (col/3, row/3)) 188 | #put gabors on image and make them the same shape as the stimuli 189 | gabors_first = Mask((col, row)).populate(gabors_first, flatten=True).reshape(Ns, -1) 190 | #normalize 191 | gabors_first=gabors_first/abs(max(np.amax(gabors_first),abs(np.amin(gabors_first)))) 192 | #gabors are added to imagearr for SVD 193 | x_first=np.vstack((imagearr,gabors_first)) 194 | 195 | #SVD 196 | print("SVD first started...") 197 | U_first, S_first, V_first = np.linalg.svd(x_first.T) 198 | print("SVD first done") 199 | 200 | #Use result of SVD to create encoders 201 | e_first = np.dot(gabors_first, U_first[:,:D]) #encoders 202 | compressed_im_first = np.dot(imagearr[:1800,:]/100, U_first[:,:D]) #D-dimensional vector reps of the images 203 | compressed_im_first = np.vstack((compressed_im_first, np.dot(imagearr[-1,:]/50, U_first[:,:D]))) 204 | 205 | np.savez('Stimuli/gabors_svd_first_exp2.npz', e_first=e_first, U_first=U_first, compressed_im_first=compressed_im_first) 206 | 207 | #same for secondary module 208 | 209 | if load_gabors_svd & os.path.isfile('Stimuli/gabors_svd_second_exp2.npz'): 210 | gabors_svd_second = np.load('Stimuli/gabors_svd_second_exp2.npz') #load stims if previously generated 211 | e_second = gabors_svd_second['e_second'] 212 | U_second = gabors_svd_second['U_second'] 213 | compressed_im_second = gabors_svd_second['compressed_im_second'] 214 | print("SVD second loaded") 215 | else: 216 | gabors_second = Gabor().generate(Ns, (col/3, row/3))#.reshape(N, -1) 217 | gabors_second = Mask((col, row)).populate(gabors_second, flatten=True).reshape(Ns, -1) 218 | gabors_second=gabors_second/abs(max(np.amax(gabors_second),abs(np.amin(gabors_second)))) 219 | x_second=np.vstack((imagearr,gabors_second)) 220 | 221 | print("SVD second started...") 222 | U_second, S_second, V_second = np.linalg.svd(x_second.T) 223 | print("SVD second done") 224 | e_second = np.dot(gabors_second, U_second[:,:D]) 225 | compressed_im_second=np.dot(imagearr[:1800,:]/100, U_second[:,:D]) 226 | compressed_im_second = np.vstack((compressed_im_second, np.dot(imagearr[-1,:]/50, U_second[:,:D]))) 227 | 228 | np.savez('Stimuli/gabors_svd_second_exp2.npz', e_second=e_second, U_second=U_second, compressed_im_second=compressed_im_second) 229 | 230 | 231 | nengo_gui_on = __name__ == 'builtins' #python3 232 | 233 | def create_model(seed=None): 234 | 235 | global model 236 | 237 | #create vocabulary to show representations in gui 238 | if nengo_gui_on: 239 | vocab_angles = spa.Vocabulary(D) 240 | for name in [0, 5, 10, 16, 24, 32, 40]: 241 | #vocab_angles.add('D' + str(name), np.linalg.norm(compressed_im_first[name+90])) #take mean across phases 242 | v = compressed_im_first[name+90] 243 | nrm = np.linalg.norm(v) 244 | if nrm > 0: 245 | v /= nrm 246 | vocab_angles.add('D' + str(name), v) #take mean across phases 247 | 248 | v = np.dot(imagearr[-1,:]/50, U_first[:,:D]) 249 | nrm = np.linalg.norm(v) 250 | if nrm > 0: 251 | v /= nrm 252 | vocab_angles.add('Impulse', v) 253 | 254 | #model = nengo.Network(seed=seed) 255 | model = spa.SPA(seed=seed) 256 | with model: 257 | 258 | #input nodes 259 | inputNode_first=nengo.Node(input_func_first,label='input_first') 260 | 261 | #sensory ensemble 262 | sensory_first = nengo.Ensemble(Ns, D, encoders=e_first, intercepts=Uniform(0.01, .1),radius=1,label='sensory_first') 263 | nengo.Connection(inputNode_first,sensory_first,transform=U_first[:,:D].T) 264 | 265 | #memory ensemble 266 | memory_first = nengo.Ensemble(Nm, D,neuron_type=stpLIF(), intercepts=Uniform(0.01, .1),radius=1,label='memory_first') 267 | nengo.Connection(sensory_first, memory_first, transform=.1) #.1) 268 | 269 | #recurrent STSP connection 270 | nengo.Connection(memory_first, memory_first,transform=1, learning_rule_type=STP(), solver=nengo.solvers.LstsqL2(weights=True)) 271 | 272 | #comparison represents sin, cosine of theta of both sensory and memory ensemble 273 | comparison_first = nengo.Ensemble(Nc, dimensions=4,radius=math.sqrt(2),intercepts=Uniform(.01, 1),label='comparison_first') 274 | nengo.Connection(sensory_first, comparison_first[:2],eval_points=compressed_im_first[0:-1],function=sincos.T) 275 | nengo.Connection(memory_first, comparison_first[2:],eval_points=compressed_im_first[0:-1],function=sincos.T) 276 | 277 | #decision represents the difference in theta decoded from the sensory and memory ensembles 278 | decision_first = nengo.Ensemble(n_neurons=Nd, dimensions=1,radius=45,label='decision_first') 279 | nengo.Connection(comparison_first, decision_first, eval_points=ep, scale_eval_points=False, function=arctan_func) 280 | 281 | #same for secondary module 282 | inputNode_second=nengo.Node(input_func_second,label='input_second') 283 | reactivate=nengo.Node(reactivate_func,label='reactivate') 284 | 285 | sensory_second = nengo.Ensemble(Ns, D, encoders=e_second, intercepts=Uniform(0.01, .1),radius=1,label='sensory_second') 286 | nengo.Connection(inputNode_second,sensory_second,transform=U_second[:,:D].T) 287 | 288 | memory_second = nengo.Ensemble(Nm, D,neuron_type=stpLIF(), intercepts=Uniform(0.01, .1),radius=1,label='memory_second') 289 | nengo.Connection(sensory_second, memory_second, transform=.1) 290 | nengo.Connection(reactivate,memory_second.neurons) #potential reactivation 291 | 292 | nengo.Connection(memory_second, memory_second,transform=1,learning_rule_type=STP(),solver=nengo.solvers.LstsqL2(weights=True)) 293 | 294 | comparison_second = nengo.Ensemble(Nd, dimensions=4,radius=math.sqrt(2),intercepts=Uniform(.01, 1),label='comparison_second') 295 | 296 | nengo.Connection(memory_second, comparison_second[2:],eval_points=compressed_im_second[0:-1],function=sincos.T) 297 | nengo.Connection(sensory_second, comparison_second[:2],eval_points=compressed_im_second[0:-1],function=sincos.T) 298 | 299 | decision_second = nengo.Ensemble(n_neurons=Nd, dimensions=1,radius=45,label='decision_second') 300 | nengo.Connection(comparison_second, decision_second, eval_points=ep, scale_eval_points=False, function=arctan_func) 301 | 302 | #decode for gui 303 | if nengo_gui_on: 304 | model.sensory_decode = spa.State(D, vocab=vocab_angles, subdimensions=12, label='sensory_decode') 305 | for ens in model.sensory_decode.all_ensembles: 306 | ens.neuron_type = nengo.Direct() 307 | nengo.Connection(sensory_first, model.sensory_decode.input,synapse=None) 308 | 309 | model.memory_decode = spa.State(D, vocab=vocab_angles, subdimensions=12, label='memory_decode') 310 | for ens in model.memory_decode.all_ensembles: 311 | ens.neuron_type = nengo.Direct() 312 | nengo.Connection(memory_first, model.memory_decode.input,synapse=None) 313 | 314 | #probes 315 | if not(nengo_gui_on): 316 | if store_representations: #sim 1 trials 1-100 317 | 318 | model.p_mem_first=nengo.Probe(memory_first, synapse=0.01) 319 | model.p_mem_second=nengo.Probe(memory_second, synapse=0.01) 320 | 321 | if store_spikes_and_resources: #sim 1 trial 1 322 | model.p_spikes_mem_first=nengo.Probe(memory_first.neurons, 'spikes') 323 | model.p_res_first=nengo.Probe(memory_first.neurons, 'resources') 324 | model.p_cal_first=nengo.Probe(memory_first.neurons, 'calcium') 325 | 326 | model.p_spikes_mem_second=nengo.Probe(memory_second.neurons, 'spikes') 327 | model.p_res_second=nengo.Probe(memory_second.neurons, 'resources') 328 | model.p_cal_second=nengo.Probe(memory_second.neurons, 'calcium') 329 | 330 | if store_decisions: #sim 2 331 | model.p_dec_first=nengo.Probe(decision_first, synapse=0.01) 332 | model.p_dec_second=nengo.Probe(decision_second, synapse=0.01) 333 | 334 | 335 | #PLOTTING CODE 336 | from nengo.utils.matplotlib import rasterplot 337 | from matplotlib import style 338 | from plotnine import * 339 | theme = theme_classic() 340 | plt.style.use('default') 341 | 342 | def plot_sim_1(sp_1,sp_2,res_1,res_2,cal_1,cal_2, mem_1, mem_2): 343 | 344 | #representations & spikes 345 | with plt.rc_context(): 346 | plt.rcParams.update(theme.rcParams) 347 | 348 | fig, axes, = plt.subplots(2,2,squeeze=True) 349 | theme.setup_figure(fig) 350 | t = sim.trange() 351 | plt.subplots_adjust(wspace=0.05, hspace=0.05) 352 | 353 | #spikes, calcium, resources first 354 | ax1=axes[0,0] 355 | ax1.set_title("First Module") 356 | ax1.set_ylabel('# cell', color='black') 357 | ax1.set_yticks(np.arange(0,Nm,500)) 358 | ax1.tick_params('y')#, colors='black') 359 | rasterplot(sim.trange(), sp_1,ax1,colors=['black']*sp_1.shape[0]) 360 | ax1.set_xticklabels([]) 361 | ax1.set_xticks([]) 362 | ax1.set_xlim(0,4.6) 363 | ax2 = ax1.twinx() 364 | ax2.plot(t, res_1, "#00bfc4",linewidth=2) 365 | ax2.plot(t, cal_1, "#e38900",linewidth=2) 366 | ax2.set_yticklabels([]) 367 | ax2.set_yticks([]) 368 | ax2.set_ylim(0,1.1) 369 | 370 | #spikes, calcium, resources second 371 | ax3=axes[0,1] 372 | ax3.set_title("Second Module") 373 | rasterplot(sim.trange(), sp_2,ax3,colors=['black']*sp_2.shape[0]) 374 | ax3.set_xticklabels([]) 375 | ax3.set_xticks([]) 376 | ax3.set_yticklabels([]) 377 | ax3.set_yticks([]) 378 | ax3.set_xlim(0,4.6) 379 | ax4 = ax3.twinx() 380 | ax4.plot(t, res_2, "#00bfc4",linewidth=2) 381 | ax4.plot(t, cal_2, "#e38900",linewidth=2) 382 | ax4.set_ylabel('synaptic variables', color="black",size=11) 383 | ax4.tick_params('y', labelcolor='#333333',labelsize=9,color='#333333') 384 | ax4.set_ylim(0,1.1) 385 | 386 | #representations first 387 | plot_mc=axes[1,0] 388 | plot_mc.plot(sim.trange(),(mem_1)); 389 | 390 | plot_mc.set_ylabel("Cosine similarity") 391 | plot_mc.set_xticks(np.arange(0.0,4.6,0.5)) 392 | plot_mc.set_xticklabels(np.arange(0,4600,500).tolist()) 393 | plot_mc.set_xlabel('time (ms)') 394 | plot_mc.set_xlim(0,4.6) 395 | colors=["#00c094","#00bfc4","#00b6eb","#06a4ff","#a58aff","#df70f8","#fb61d7", "#c49a00"] 396 | for i,j in enumerate(plot_mc.lines): 397 | j.set_color(colors[i]) 398 | 399 | #representations uncued 400 | plot_mu=axes[1,1] 401 | 402 | plot_mu.plot(sim.trange(),(mem_2)); 403 | plot_mu.set_xticks(np.arange(0.0,4.6,0.5)) 404 | plot_mu.set_xticklabels(np.arange(0,4600,500).tolist()) 405 | plot_mu.set_xlabel('time (ms)') 406 | plot_mu.set_yticks([]) 407 | plot_mu.set_yticklabels([]) 408 | plot_mu.set_xlim(0,4.6) 409 | for i,j in enumerate(plot_mu.lines): 410 | j.set_color(colors[i]) 411 | plot_mu.legend(["0°","5°","10°","16°","24°","32°","40°", "Impulse"], title="Stimulus", bbox_to_anchor=(1.02, -0.25, .30, 0.8), loc=3, 412 | ncol=1, mode="expand", borderaxespad=0.) 413 | 414 | fig.set_size_inches(11, 5) 415 | theme.apply(plt.gcf().axes[0]) 416 | theme.apply(plt.gcf().axes[1]) 417 | theme.apply(plt.gcf().axes[2]) 418 | theme.apply(plt.gcf().axes[3]) 419 | plt.savefig('representations_exp2_2003.eps', format='eps', dpi=1000) 420 | plt.show() 421 | 422 | 423 | # impulse 1 424 | with plt.rc_context(): 425 | plt.rcParams.update(theme.rcParams) 426 | 427 | fig, axes, = plt.subplots(1,2,squeeze=True) 428 | theme.setup_figure(fig) 429 | t = sim.trange() 430 | plt.subplots_adjust(wspace=0.1, hspace=0.05) 431 | 432 | plot_mc=axes[0] 433 | plot_mc.set_title("First Module") 434 | plot_mc.plot(sim.trange(),(mem_1)); 435 | plot_mc.set_ylabel("Cosine similarity") 436 | plot_mc.set_xticks(np.arange(1.2,1.4,0.05)) 437 | plot_mc.set_xticklabels(np.arange(0,250,50).tolist()) 438 | plot_mc.set_xlabel('time after onset impulse (ms)') 439 | plot_mc.set_xlim(1.2,1.35) 440 | plot_mc.set_ylim(0,0.9) 441 | colors=["#00c094","#00bfc4","#00b6eb","#06a4ff","#a58aff","#df70f8","#fb61d7", "#c49a00"] 442 | for i,j in enumerate(plot_mc.lines): 443 | j.set_color(colors[i]) 444 | 445 | plot_mu=axes[1] 446 | plot_mu.set_title("Second Module") 447 | plot_mu.plot(sim.trange(),(mem_2)); 448 | plot_mu.set_xticks(np.arange(1.2,1.4,0.05)) 449 | plot_mu.set_xticklabels(np.arange(0,250,50).tolist()) 450 | plot_mu.set_xlabel('time after onset impulse (ms)') 451 | plot_mu.set_yticks([]) 452 | plot_mu.set_yticklabels([]) 453 | plot_mu.set_xlim(1.2,1.35) 454 | plot_mu.set_ylim(0,0.9) 455 | for i,j in enumerate(plot_mu.lines): 456 | j.set_color(colors[i]) 457 | plot_mu.legend(["0°","5°","10°","16°","24°","32°","40°", "Impulse"], title="Stimulus", bbox_to_anchor=(0.85, 0.25, .55, 0.8), loc=3, 458 | ncol=1, mode="expand", borderaxespad=0.) 459 | 460 | fig.set_size_inches(6, 4) 461 | 462 | theme.apply(plt.gcf().axes[0]) 463 | theme.apply(plt.gcf().axes[1]) 464 | plt.savefig('Impulse1_exp2_2003.eps', format='eps', dpi=1000) 465 | plt.show() 466 | 467 | # impulse 2 468 | with plt.rc_context(): 469 | plt.rcParams.update(theme.rcParams) 470 | 471 | fig, axes, = plt.subplots(1,2,squeeze=True) 472 | theme.setup_figure(fig) 473 | t = sim.trange() 474 | plt.subplots_adjust(wspace=0.1, hspace=0.05) 475 | 476 | plot_mc=axes[0] 477 | plot_mc.set_title("First Module") 478 | plot_mc.plot(sim.trange(),(mem_1)); 479 | plot_mc.set_ylabel("Cosine similarity") 480 | plot_mc.set_xticks(np.arange(3.8,4.0,0.05)) 481 | plot_mc.set_xticklabels(np.arange(0,250,50).tolist()) 482 | plot_mc.set_xlabel('time after onset impulse (ms)') 483 | plot_mc.set_xlim(3.8,3.95) 484 | plot_mc.set_ylim(0,0.9) 485 | colors=["#00c094","#00bfc4","#00b6eb","#06a4ff","#a58aff","#df70f8","#fb61d7", "#c49a00"] 486 | for i,j in enumerate(plot_mc.lines): 487 | j.set_color(colors[i]) 488 | 489 | plot_mu=axes[1] 490 | plot_mu.set_title("Second Module") 491 | plot_mu.plot(sim.trange(),(mem_2)); 492 | plot_mu.set_xticks(np.arange(3.8,4.0,0.05)) 493 | plot_mu.set_xticklabels(np.arange(0,250,50).tolist()) 494 | plot_mu.set_xlabel('time after onset impulse (ms)') 495 | plot_mu.set_yticks([]) 496 | plot_mu.set_yticklabels([]) 497 | plot_mu.set_xlim(3.8,3.95) 498 | plot_mu.set_ylim(0,0.9) 499 | for i,j in enumerate(plot_mu.lines): 500 | j.set_color(colors[i]) 501 | plot_mu.legend(["0°","5°","10°","16°","24°","32°","40°", "Impulse"], title="Stimulus", bbox_to_anchor=(0.85, 0.25, .55, 0.8), loc=3, 502 | ncol=1, mode="expand", borderaxespad=0.) 503 | 504 | fig.set_size_inches(6, 4) 505 | 506 | theme.apply(plt.gcf().axes[0]) 507 | theme.apply(plt.gcf().axes[1]) 508 | plt.savefig('Impulse2_exp2_2003.eps', format='eps', dpi=1000) 509 | plt.show() 510 | 511 | 512 | 513 | #SIMULATION 514 | #note that this is split for running a single trial in the nengo gui, and a full simulation 515 | 516 | 517 | #normalise stimuli to be between 0 and 180 degrees orientation 518 | def norm_p(p): 519 | if p<0: 520 | return 180+p 521 | if p>180: 522 | return p-180 523 | else: 524 | return p 525 | 526 | #Calculate normalised cosine similarity and avoid divide by 0 errors 527 | def cosine_sim(a,b): 528 | out=np.zeros(a.shape[0]) 529 | for i in range(0, a.shape[0]): 530 | if abs(np.linalg.norm(a[i])) > 0.05: 531 | out[i]=abs(np.dot(a[i], b)/(np.linalg.norm(a[i])*np.linalg.norm(b))) 532 | return out 533 | 534 | 535 | 536 | if nengo_gui_on: 537 | generate_gabors() #generate gabors 538 | create_model(seed=0) #build model 539 | 540 | memory_item_first = 0 + 90 541 | probe_first = 40 + 90 542 | memory_item_second = 0 + 90 543 | probe_second = 40 + 90 544 | 545 | else: #no gui 546 | 547 | #path 548 | cur_path = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))+'/data_exp2/' #store output in data subfolder 549 | 550 | #simulation 1 551 | if sim_to_run == 1: 552 | 553 | print('Running simulation 1') 554 | print('') 555 | 556 | load_gabors_svd = False #no need to randomize this 557 | 558 | ntrials = 100 559 | store_representations = True 560 | store_decisions = False 561 | 562 | #store results 563 | templates = np.array([0, 5, 10, 16, 24, 32, 40]) + 90 564 | mem_1 = np.zeros((4600,len(templates)+1)) #keep cosine sim for 9 items 565 | mem_2 = np.zeros((4600,len(templates)+1)) 566 | 567 | #first, run 100 trials to get average cosine sim 568 | for run in range(ntrials): 569 | 570 | print('Run ' + str(run+1)) 571 | 572 | #stimuli 573 | 574 | phase = 180*(run % 10) 575 | memory_item_first = 0 + 90 + phase 576 | probe_first = 40 + 90 + phase 577 | memory_item_second = 0 + 90 + phase 578 | probe_second = 40 + 90 + phase 579 | 580 | #create new gabor filters every 10 trials 581 | if run % 10 == 0: 582 | generate_gabors() 583 | 584 | create_model(seed=run) 585 | sim = StpOCLsimulator(network=model, seed=run, context=context,progress_bar=False) 586 | 587 | #run simulation 588 | sim.run(4.6) 589 | 590 | #reset simulator, clean probes thoroughly 591 | #print(sim.data[model.p_mem_cued].shape) 592 | #calc cosine sim with templates 593 | temp_phase = list(templates + phase) + [1800] 594 | for cnt, templ in enumerate(temp_phase): 595 | mem_1[:,cnt] += cosine_sim(sim.data[model.p_mem_first][:,:,],compressed_im_first[templ,:]) 596 | mem_2[:,cnt] += cosine_sim(sim.data[model.p_mem_second][:,:,],compressed_im_second[templ,:]) 597 | 598 | sim.reset() 599 | for probe2 in sim.model.probes: 600 | del sim._probe_outputs[probe2][:] 601 | del sim.data 602 | sim.data = nengo.simulator.ProbeDict(sim._probe_outputs) 603 | 604 | 605 | #average 606 | mem_1 /= ntrials 607 | mem_2 /= ntrials 608 | 609 | #second, run 1 trial to get calcium and spikes 610 | store_spikes_and_resources = True 611 | store_representations = False 612 | create_model(seed=0) #recreate model to change probes 613 | sim = StpOCLsimulator(network=model, seed=0, context=context,progress_bar=False) 614 | 615 | print('Run ' + str(ntrials+1)) 616 | sim.run(4.6) 617 | 618 | #store spikes and calcium 619 | sp_1 = sim.data[model.p_spikes_mem_first] 620 | res_1=np.mean(sim.data[model.p_res_first][:,:,],1) #take mean over neurons 621 | cal_1=np.mean(sim.data[model.p_cal_first][:,:,],1) #take mean over neurons 622 | 623 | sp_2=sim.data[model.p_spikes_mem_second] 624 | res_2=np.mean(sim.data[model.p_res_second][:,:,],1) 625 | cal_2=np.mean(sim.data[model.p_cal_second][:,:,],1) 626 | 627 | #plot 628 | plot_sim_1(sp_1,sp_2,res_1,res_2,cal_1,cal_2, mem_1, mem_2) 629 | 630 | 631 | #simulation 2 632 | if sim_to_run == 2: 633 | 634 | load_gabors_svd = False #set to false for real simulation 635 | 636 | n_subj = 19 637 | trials_per_subj = 2*864 638 | store_representations = False 639 | store_decisions = True 640 | 641 | #np array to keep track of the input during the simulation runs 642 | initialangle_c = np.zeros(n_subj*trials_per_subj) #cued 643 | angle_index=0 644 | 645 | #orientation differences between probe and memory item for each run 646 | probelist=[-40, -32, -24, -16, -10, -5, 5, 10, 16, 24, 32, 40] 647 | 648 | for subj in range(n_subj): 649 | 650 | #create new gabor filters and model for each new participant 651 | generate_gabors() 652 | create_model(seed=subj) 653 | 654 | #use StpOCLsimulator to make use of the Nengo OCL implementation of STSP 655 | sim = StpOCLsimulator(network=model, seed=subj, context=context,progress_bar=False) 656 | 657 | #trials come in sets of 12, which we call a run (all possible orientation differences between memory and probe), 658 | runs = int(trials_per_subj / 12) 659 | 660 | for run in range(runs): 661 | 662 | #run a trial with each possible orientation difference 663 | for cnt_in_run, anglediff in enumerate(probelist): 664 | 665 | print('Subject ' + str(subj+1) + '/' + str(n_subj) + '; Trial ' + str(run*12 + cnt_in_run + 1) + '/' + str(trials_per_subj)) 666 | 667 | #set probe and stim 668 | memory_item_first=randint(0, 179) #random memory 669 | probe_first=memory_item_first+anglediff #probe based on that 670 | probe_first=norm_p(probe_first) #normalise probe 671 | 672 | #random phase 673 | or_memory_item_first=memory_item_first #original 674 | memory_item_first=memory_item_first+(180*randint(0, 9)) 675 | probe_first=probe_first+(180*randint(0, 9)) 676 | 677 | #same for secondary item 678 | memory_item_second = memory_item_first 679 | probe_second = probe_first 680 | 681 | #run simulation 682 | sim.run(4.6) 683 | 684 | #store output 685 | np.savetxt(cur_path+sim_no+"_Diff_Theta_%i_subj_%i_trial_%i_probe1.csv" % (anglediff, subj+1, run*12+cnt_in_run+1), sim.data[model.p_dec_first][1800:1900,:], delimiter=",") 686 | np.savetxt(cur_path+sim_no+"_Diff_Theta_%i_subj_%i_trial_%i_probe2.csv" % (anglediff, subj+1, run*12+cnt_in_run+1), sim.data[model.p_dec_second][4300:4400,:], delimiter=",") 687 | 688 | #reset simulator, clean probes thoroughly 689 | sim.reset() 690 | for probe2 in sim.model.probes: 691 | del sim._probe_outputs[probe2][:] 692 | del sim.data 693 | sim.data = nengo.simulator.ProbeDict(sim._probe_outputs) 694 | 695 | angle_index=angle_index+1 696 | 697 | -------------------------------------------------------------------------------- /Model_sim_exp2.py.cfg: -------------------------------------------------------------------------------- 1 | _viz_0 = nengo_gui.components.SpaSimilarity(model.sensory_decode,args='default') 2 | _viz_config[_viz_0].max_value = 1 3 | _viz_config[_viz_0].min_value = -0.2 4 | _viz_config[_viz_0].show_pairs = False 5 | _viz_config[_viz_0].x = -0.05369369956299075 6 | _viz_config[_viz_0].y = 0.09760405042857319 7 | _viz_config[_viz_0].width = 0.07935918403792248 8 | _viz_config[_viz_0].height = 0.13926825209046173 9 | _viz_config[_viz_0].label_visible = True 10 | _viz_2 = nengo_gui.components.Value(model.ensembles[7]) 11 | _viz_config[_viz_2].max_value = 10 12 | _viz_config[_viz_2].min_value = -10 13 | _viz_config[_viz_2].show_legend = False 14 | _viz_config[_viz_2].legend_labels = ['label_0'] 15 | _viz_config[_viz_2].synapse = 0.01 16 | _viz_config[_viz_2].x = 1.2356759298146966 17 | _viz_config[_viz_2].y = 0.9821406562779724 18 | _viz_config[_viz_2].width = 0.07935918403792248 19 | _viz_config[_viz_2].height = 0.13926825209046173 20 | _viz_config[_viz_2].label_visible = True 21 | _viz_3 = nengo_gui.components.Value(model.ensembles[3]) 22 | _viz_config[_viz_3].max_value = 10 23 | _viz_config[_viz_3].min_value = -10 24 | _viz_config[_viz_3].show_legend = False 25 | _viz_config[_viz_3].legend_labels = ['label_0'] 26 | _viz_config[_viz_3].synapse = 0.01 27 | _viz_config[_viz_3].x = 1.1991178890162815 28 | _viz_config[_viz_3].y = 0.20915588671572496 29 | _viz_config[_viz_3].width = 0.1216628990759502 30 | _viz_config[_viz_3].height = 0.19412416693242154 31 | _viz_config[_viz_3].label_visible = True 32 | _viz_4 = nengo_gui.components.Value(model.ensembles[5]) 33 | _viz_config[_viz_4].max_value = 1 34 | _viz_config[_viz_4].min_value = -1 35 | _viz_config[_viz_4].show_legend = False 36 | _viz_config[_viz_4].legend_labels = ['label_0', 'label_1', 'label_2', 'label_3', 'label_4', 'label_5', 'label_6', 'label_7', 'label_8', 'label_9', 'label_10', 'label_11', 'label_12', 'label_13', 'label_14', 'label_15', 'label_16', 'label_17', 'label_18', 'label_19', 'label_20', 'label_21', 'label_22', 'label_23'] 37 | _viz_config[_viz_4].synapse = 0.01 38 | _viz_config[_viz_4].x = 1.2343738914970723 39 | _viz_config[_viz_4].y = 1.3245062342075613 40 | _viz_config[_viz_4].width = 0.07935918403792248 41 | _viz_config[_viz_4].height = 0.13926825209046173 42 | _viz_config[_viz_4].label_visible = True 43 | _viz_5 = nengo_gui.components.SpaSimilarity(model.memory_decode,args='default') 44 | _viz_config[_viz_5].max_value = 1 45 | _viz_config[_viz_5].min_value = -0.2 46 | _viz_config[_viz_5].show_pairs = False 47 | _viz_config[_viz_5].x = 0.19262302301025083 48 | _viz_config[_viz_5].y = 1.184674484969335 49 | _viz_config[_viz_5].width = 0.07935918403792248 50 | _viz_config[_viz_5].height = 0.13926825209046173 51 | _viz_config[_viz_5].label_visible = True 52 | _viz_net_graph = nengo_gui.components.NetGraph() 53 | _viz_progress = nengo_gui.components.Progress() 54 | _viz_config[_viz_progress].x = 0 55 | _viz_config[_viz_progress].y = 0 56 | _viz_config[_viz_progress].width = 100 57 | _viz_config[_viz_progress].height = 100 58 | _viz_config[_viz_progress].label_visible = True 59 | _viz_sim_control = nengo_gui.components.SimControl() 60 | _viz_config[_viz_sim_control].shown_time = 0.5 61 | _viz_config[_viz_sim_control].kept_time = 4.0 62 | _viz_config[model].pos=(0.215376711776156, 0.11438011215863245) 63 | _viz_config[model].size=(0.6131842341444002, 0.6131842341444002) 64 | _viz_config[model].expanded=True 65 | _viz_config[model].has_layout=True 66 | _viz_config[model.ensembles[0]].pos=(0.33041123391021066, 0.48315238372474056) 67 | _viz_config[model.ensembles[0]].size=(0.053763440860215055, 0.1020408163265306) 68 | _viz_config[model.ensembles[1]].pos=(0.4818870288654669, 0.8044761538057824) 69 | _viz_config[model.ensembles[1]].size=(0.053763440860215055, 0.1020408163265306) 70 | _viz_config[model.ensembles[2]].pos=(0.6054048905297307, 0.485000237818829) 71 | _viz_config[model.ensembles[2]].size=(0.053763440860215055, 0.1020408163265306) 72 | _viz_config[model.ensembles[3]].pos=(1.048055801017249, 0.5093736535043252) 73 | _viz_config[model.ensembles[3]].size=(0.053763440860215055, 0.1020408163265306) 74 | _viz_config[model.ensembles[4]].pos=(0.2984180231506136, 0.8296292330013689) 75 | _viz_config[model.ensembles[4]].size=(0.1, 0.1) 76 | _viz_config[model.ensembles[5]].pos=(0.8122903031237874, 0.7718444464771659) 77 | _viz_config[model.ensembles[5]].size=(0.1, 0.1) 78 | _viz_config[model.ensembles[6]].pos=(0.8424737289497963, 1.2755228855379894) 79 | _viz_config[model.ensembles[6]].size=(0.1, 0.1) 80 | _viz_config[model.ensembles[7]].pos=(1.0333180822464096, 1.062498023542855) 81 | _viz_config[model.ensembles[7]].size=(0.1, 0.1) 82 | _viz_config[model.memory_decode].pos=(0.48406219827730346, 1.1842807378985376) 83 | _viz_config[model.memory_decode].size=(0.07199178797724101, 0.11543568040366908) 84 | _viz_config[model.memory_decode].expanded=False 85 | _viz_config[model.memory_decode].has_layout=False 86 | _viz_config[model.memory_decode.state_ensembles].expanded=False 87 | _viz_config[model.memory_decode.state_ensembles].has_layout=False 88 | _viz_config[model.nodes[0]].pos=(0.06961257909962328, 0.48110559131271463) 89 | _viz_config[model.nodes[0]].size=(0.043010752688172046, 0.08163265306122448) 90 | _viz_config[model.nodes[1]].pos=(0.06989247311827958, 0.7959183673469387) 91 | _viz_config[model.nodes[1]].size=(0.043010752688172046, 0.08163265306122448) 92 | _viz_config[model.nodes[2]].pos=(0.8256823459983408, 0.053693233104864624) 93 | _viz_config[model.nodes[2]].size=(0.1, 0.1) 94 | _viz_config[model.sensory_decode].pos=(0.33258900053375823, 0.06822915683220981) 95 | _viz_config[model.sensory_decode].size=(0.07711293532218821, 0.08724346412454488) 96 | _viz_config[model.sensory_decode].expanded=False 97 | _viz_config[model.sensory_decode].has_layout=False 98 | _viz_config[model.sensory_decode.state_ensembles].expanded=False 99 | _viz_config[model.sensory_decode.state_ensembles].has_layout=False -------------------------------------------------------------------------------- /Model_sim_exp3.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from PIL import Image 3 | import matplotlib.pyplot as plt 4 | from nengo.dists import Uniform 5 | import nengo 6 | import math 7 | from stp_ocl_implementation import * 8 | import os, inspect 9 | from nengo_extras.vision import Gabor, Mask 10 | from random import randint 11 | import nengo.spa as spa 12 | import os.path 13 | 14 | #SIMULATION CONTROL for GUI 15 | uncued=False #set if you want to run both the cued and uncued model 16 | load_gabors_svd=True #set to false if you want to generate new ones 17 | store_representations = False #store representations of model runs (for Fig 3 & 4) 18 | store_decisions = False #store decision ensemble (for Fig 5 & 6) 19 | store_spikes_and_resources = False #store spikes, calcium etc. (Fig 3 & 4) 20 | 21 | #specify here which sim you want to run if you do not use the nengo GUI 22 | #1 = behavioral study with different SOAs, exp 3, Fig S4 in Wolff et all 23 | sim_to_run = 1 24 | sim_no="1" 25 | impulse = '1000' #1000 = white, 999 = original black 26 | 27 | 28 | #set this if you are using nengo OCL 29 | platform = cl.get_platforms()[0] #select platform, should be 0 30 | device=platform.get_devices()[2] #select GPU, use 0 (Nvidia 1) or 1 (Nvidia 3) 31 | context=cl.Context([device]) 32 | 33 | 34 | 35 | #MODEL PARAMETERS 36 | D = 24 #dimensions of representations 37 | Ns = 1000 #number of neurons in sensory layer 38 | Nm = 1500 #number of neurons in memory layer 39 | Nc = 1500 #number of neurons in comparison 40 | Nd = 1000 #number of neurons in decision 41 | 42 | 43 | #LOAD INPUT STIMULI (images created using the psychopy package) 44 | #(Stimuli should be in a subfolder named 'Stimuli') 45 | 46 | #width and height of images 47 | diameter=col=row=128 48 | 49 | #load grating stimuli 50 | angles=np.arange(-90,90,1) #rotation 51 | phases=np.arange(0,1,0.1) #phase 52 | 53 | try: 54 | imagearr = np.load('Stimuli/all_stims_exp3_' + str(impulse) + '.npy') #load stims if previously generated 55 | except FileNotFoundError: #or generate 56 | imagearr=np.zeros((0,diameter**2)) 57 | for phase in phases: 58 | for angle in angles: 59 | name="Stimuli/stim"+str(angle)+"_"+str(round(phase,1))+".png" 60 | img=Image.open(name) 61 | img=np.array(img.convert('L')) 62 | imagearr=np.vstack((imagearr,img.ravel())) 63 | 64 | #use impulse 65 | name="Stimuli/stim" + str(impulse) + ".png" 66 | img=Image.open(name) 67 | img=np.array(img.convert('L')) 68 | imagearr=np.vstack((imagearr,img.ravel())) 69 | 70 | #normalize to be between -1 and 1 71 | imagearr=imagearr/255 72 | imagearr=2 * imagearr - 1 73 | 74 | #imagearr is a (1801, 16384) np array containing all stimuli + the impulse 75 | np.save('Stimuli/all_stims_exp3_' + str(impulse) + '.npy',imagearr) 76 | 77 | 78 | 79 | #INPUT FUNCTIONS 80 | 81 | #set default input 82 | memory_item_cued = 0 83 | probe_cued = 0 84 | memory_item_uncued = 0 85 | probe_uncued = 0 86 | 87 | start_impulse = -1 88 | end_impulse = -1 89 | 90 | #input stimuli 91 | #250 ms memory items | 0-250 92 | #800 ms fixation | 250-1050 93 | #20 ms reactivation | 1050-1070 94 | #1080 ms fixation | 1070-2150 95 | #250 ms probe | 2650-2900 96 | def input_func_cued(t): 97 | if t > 0 and t < 0.25: 98 | return imagearr[memory_item_cued,:]/100 99 | elif t > start_impulse and t < end_impulse: 100 | return imagearr[-1,:]/50 #impulse, twice the contrast of other items 101 | elif t > 2.65 and t < 2.90: 102 | return imagearr[probe_cued,:]/100 103 | else: 104 | return np.zeros(128*128) #blank screen 105 | 106 | def input_func_uncued(t): 107 | if t > 0 and t < 0.25: 108 | return imagearr[memory_item_uncued,:]/100 109 | elif t > start_impulse and t < end_impulse: 110 | return imagearr[-1,:]/50 #impulse, twice the contrast of other items 111 | elif t > 2.65 and t < 2.90: 112 | return imagearr[probe_uncued,:]/100 113 | else: 114 | return np.zeros(128*128) #blank screen 115 | 116 | #reactivate memory cued ensemble with nonspecific signal 117 | def reactivate_func(t): 118 | if t>1.050 and t<1.070: 119 | return np.ones(Nm)*0.0200 120 | else: 121 | return np.zeros(Nm) 122 | 123 | #Create matrix of sine and cosine values associated with the stimuli 124 | #so that we can later specify a transform from stimuli to rotation 125 | Fa = np.tile(angles,phases.size) #want to do this for each phase 126 | Frad = (Fa/90) * math.pi #make radians 127 | Sin = np.sin(Frad) 128 | Cos = np.cos(Frad) 129 | sincos = np.vstack((Sin,Cos)) #sincos 130 | 131 | #Create eval points so that we can go from sine and cosine of theta in sensory and memory layer 132 | #to the difference in theta between the two 133 | samples = 10000 134 | sinAcosA = nengo.dists.UniformHypersphere(surface=True).sample(samples,2) 135 | thetaA = np.arctan2(sinAcosA[:,0],sinAcosA[:,1]) 136 | thetaDiff = (90*np.random.random(samples)-45)/180*np.pi 137 | thetaB = thetaA + thetaDiff 138 | 139 | sinBcosB = np.vstack((np.sin(thetaB),np.cos(thetaB))) 140 | scale = np.random.random(samples)*0.9+0.1 141 | sinBcosB = sinBcosB * scale 142 | ep = np.hstack((sinAcosA,sinBcosB.T)) 143 | 144 | 145 | #continuous variant of arctan(a,b)-arctan(c,d) 146 | def arctan_func(v): 147 | yA, xA, yB, xB = v 148 | z = np.arctan2(yA, xA) - np.arctan2(yB, xB) 149 | pos_ans = [z, z+2*np.pi, z-2*np.pi] 150 | i = np.argmin(np.abs(pos_ans)) 151 | return pos_ans[i]*90/math.pi 152 | 153 | 154 | 155 | #MODEL 156 | 157 | #gabor generation for a particular model-participant 158 | def generate_gabors(): 159 | 160 | global e_cued 161 | global U_cued 162 | global compressed_im_cued 163 | 164 | global e_uncued 165 | global U_uncued 166 | global compressed_im_uncued 167 | 168 | #to speed things up, load previously generated ones 169 | if load_gabors_svd & os.path.isfile('Stimuli/gabors_svd_cued_exp3.npz'): 170 | gabors_svd_cued = np.load('Stimuli/gabors_svd_cued_exp3.npz') #load stims if previously generated 171 | e_cued = gabors_svd_cued['e_cued'] 172 | U_cued = gabors_svd_cued['U_cued'] 173 | compressed_im_cued = gabors_svd_cued['compressed_im_cued'] 174 | print("SVD cued loaded") 175 | 176 | else: #or generate and save 177 | 178 | #cued module 179 | #for each neuron in the sensory layer, generate a Gabor of 1/3 of the image size 180 | gabors_cued = Gabor().generate(Ns, (col/3, row/3)) 181 | #put gabors on image and make them the same shape as the stimuli 182 | gabors_cued = Mask((col, row)).populate(gabors_cued, flatten=True).reshape(Ns, -1) 183 | #normalize 184 | gabors_cued=gabors_cued/abs(max(np.amax(gabors_cued),abs(np.amin(gabors_cued)))) 185 | #gabors are added to imagearr for SVD 186 | x_cued=np.vstack((imagearr,gabors_cued)) 187 | 188 | #SVD 189 | print("SVD cued started...") 190 | U_cued, S_cued, V_cued = np.linalg.svd(x_cued.T) 191 | print("SVD cued done") 192 | 193 | #Use result of SVD to create encoders 194 | e_cued = np.dot(gabors_cued, U_cued[:,:D]) #encoders 195 | compressed_im_cued = np.dot(imagearr[:1800,:]/100, U_cued[:,:D]) #D-dimensional vector reps of the images 196 | compressed_im_cued = np.vstack((compressed_im_cued, np.dot(imagearr[-1,:]/50, U_cued[:,:D]))) 197 | 198 | np.savez('Stimuli/gabors_svd_cued_exp3.npz', e_cued=e_cued, U_cued=U_cued, compressed_im_cued=compressed_im_cued) 199 | 200 | #same for uncued module 201 | if uncued: 202 | 203 | if load_gabors_svd & os.path.isfile('Stimuli/gabors_svd_uncued_exp3.npz'): 204 | gabors_svd_uncued = np.load('Stimuli/gabors_svd_uncued_exp3.npz') #load stims if previously generated 205 | e_uncued = gabors_svd_uncued['e_uncued'] 206 | U_uncued = gabors_svd_uncued['U_uncued'] 207 | compressed_im_uncued = gabors_svd_uncued['compressed_im_uncued'] 208 | print("SVD uncued loaded") 209 | else: 210 | gabors_uncued = Gabor().generate(Ns, (col/3, row/3))#.reshape(N, -1) 211 | gabors_uncued = Mask((col, row)).populate(gabors_uncued, flatten=True).reshape(Ns, -1) 212 | gabors_uncued=gabors_uncued/abs(max(np.amax(gabors_uncued),abs(np.amin(gabors_uncued)))) 213 | x_uncued=np.vstack((imagearr,gabors_uncued)) 214 | 215 | print("SVD uncued started...") 216 | U_uncued, S_uncued, V_uncued = np.linalg.svd(x_uncued.T) 217 | print("SVD uncued done") 218 | e_uncued = np.dot(gabors_uncued, U_uncued[:,:D]) 219 | compressed_im_uncued=np.dot(imagearr[:1800,:]/100, U_uncued[:,:D]) 220 | compressed_im_uncued = np.vstack((compressed_im_uncued, np.dot(imagearr[-1,:]/50, U_uncued[:,:D]))) 221 | 222 | np.savez('Stimuli/gabors_svd_uncued_exp3.npz', e_uncued=e_uncued, U_uncued=U_uncued, compressed_im_uncued=compressed_im_uncued) 223 | 224 | 225 | nengo_gui_on = __name__ == 'builtins' #python3 226 | 227 | def create_model(seed=None): 228 | 229 | global model 230 | 231 | #create vocabulary to show representations in gui 232 | if nengo_gui_on: 233 | vocab_angles = spa.Vocabulary(D) 234 | for name in [0, 3, 7, 12, 18, 25, 33, 42]: 235 | #vocab_angles.add('D' + str(name), np.linalg.norm(compressed_im_cued[name+90])) #take mean across phases 236 | v = compressed_im_cued[name+90] 237 | nrm = np.linalg.norm(v) 238 | if nrm > 0: 239 | v /= nrm 240 | vocab_angles.add('D' + str(name), v) #take mean across phases 241 | 242 | v = np.dot(imagearr[-1,:]/50, U_cued[:,:D]) 243 | nrm = np.linalg.norm(v) 244 | if nrm > 0: 245 | v /= nrm 246 | vocab_angles.add('Impulse', v) 247 | 248 | #model = nengo.Network(seed=seed) 249 | model = spa.SPA(seed=seed) 250 | with model: 251 | 252 | #input nodes 253 | inputNode_cued=nengo.Node(input_func_cued,label='input_cued') 254 | reactivate=nengo.Node(reactivate_func,label='reactivate') 255 | 256 | #sensory ensemble 257 | sensory_cued = nengo.Ensemble(Ns, D, encoders=e_cued, intercepts=Uniform(0.01, .1),radius=1,label='sensory_cued') 258 | nengo.Connection(inputNode_cued,sensory_cued,transform=U_cued[:,:D].T) 259 | 260 | #memory ensemble 261 | memory_cued = nengo.Ensemble(Nm, D,neuron_type=stpLIF(), intercepts=Uniform(0.01, .1),radius=1,label='memory_cued') 262 | nengo.Connection(reactivate,memory_cued.neurons) #potential reactivation 263 | nengo.Connection(sensory_cued, memory_cued, transform=.1) #.1) 264 | 265 | #recurrent STSP connection 266 | nengo.Connection(memory_cued, memory_cued,transform=1, learning_rule_type=STP(), solver=nengo.solvers.LstsqL2(weights=True)) 267 | 268 | #comparison represents sin, cosine of theta of both sensory and memory ensemble 269 | comparison_cued = nengo.Ensemble(Nc, dimensions=4,radius=math.sqrt(2),intercepts=Uniform(.01, 1),label='comparison_cued') 270 | nengo.Connection(sensory_cued, comparison_cued[:2],eval_points=compressed_im_cued[0:-1],function=sincos.T) 271 | nengo.Connection(memory_cued, comparison_cued[2:],eval_points=compressed_im_cued[0:-1],function=sincos.T) 272 | 273 | #decision represents the difference in theta decoded from the sensory and memory ensembles 274 | decision_cued = nengo.Ensemble(n_neurons=Nd, dimensions=1,radius=45,label='decision_cued') 275 | nengo.Connection(comparison_cued, decision_cued, eval_points=ep, scale_eval_points=False, function=arctan_func) 276 | 277 | #same for uncued 278 | if uncued: 279 | inputNode_uncued=nengo.Node(input_func_uncued,label='input_uncued') 280 | 281 | sensory_uncued = nengo.Ensemble(Ns, D, encoders=e_uncued, intercepts=Uniform(0.01, .1),radius=1,label='sensory_uncued') 282 | nengo.Connection(inputNode_uncued,sensory_uncued,transform=U_uncued[:,:D].T) 283 | 284 | memory_uncued = nengo.Ensemble(Nm, D,neuron_type=stpLIF(), intercepts=Uniform(0.01, .1),radius=1,label='memory_uncued') 285 | nengo.Connection(sensory_uncued, memory_uncued, transform=.1) 286 | 287 | nengo.Connection(memory_uncued, memory_uncued,transform=1,learning_rule_type=STP(),solver=nengo.solvers.LstsqL2(weights=True)) 288 | 289 | comparison_uncued = nengo.Ensemble(Nd, dimensions=4,radius=math.sqrt(2),intercepts=Uniform(.01, 1),label='comparison_uncued') 290 | 291 | nengo.Connection(memory_uncued, comparison_uncued[2:],eval_points=compressed_im_uncued[0:-1],function=sincos.T) 292 | nengo.Connection(sensory_uncued, comparison_uncued[:2],eval_points=compressed_im_uncued[0:-1],function=sincos.T) 293 | 294 | decision_uncued = nengo.Ensemble(n_neurons=Nd, dimensions=1,radius=45,label='decision_uncued') 295 | nengo.Connection(comparison_uncued, decision_uncued, eval_points=ep, scale_eval_points=False, function=arctan_func) 296 | 297 | #decode for gui 298 | if nengo_gui_on: 299 | model.sensory_decode = spa.State(D, vocab=vocab_angles, subdimensions=12, label='sensory_decode') 300 | for ens in model.sensory_decode.all_ensembles: 301 | ens.neuron_type = nengo.Direct() 302 | nengo.Connection(sensory_cued, model.sensory_decode.input,synapse=None) 303 | 304 | model.memory_decode = spa.State(D, vocab=vocab_angles, subdimensions=12, label='memory_decode') 305 | for ens in model.memory_decode.all_ensembles: 306 | ens.neuron_type = nengo.Direct() 307 | nengo.Connection(memory_cued, model.memory_decode.input,synapse=None) 308 | 309 | #probes 310 | if not(nengo_gui_on): 311 | if store_representations: #sim 1 trials 1-100 312 | #p_dtheta_cued=nengo.Probe(decision_cued, synapse=0.01) 313 | model.p_mem_cued=nengo.Probe(memory_cued, synapse=0.01) 314 | #p_sen_cued=nengo.Probe(sensory_cued, synapse=0.01) 315 | 316 | if uncued: 317 | model.p_mem_uncued=nengo.Probe(memory_uncued, synapse=0.01) 318 | 319 | if store_spikes_and_resources: #sim 1 trial 1 320 | model.p_spikes_mem_cued=nengo.Probe(memory_cued.neurons, 'spikes') 321 | model.p_res_cued=nengo.Probe(memory_cued.neurons, 'resources') 322 | model.p_cal_cued=nengo.Probe(memory_cued.neurons, 'calcium') 323 | 324 | if uncued: 325 | model.p_spikes_mem_uncued=nengo.Probe(memory_uncued.neurons, 'spikes') 326 | model.p_res_uncued=nengo.Probe(memory_uncued.neurons, 'resources') 327 | model.p_cal_uncued=nengo.Probe(memory_uncued.neurons, 'calcium') 328 | 329 | if store_decisions: #sim 2 330 | model.p_dec_cued=nengo.Probe(decision_cued, synapse=0.01) 331 | 332 | 333 | 334 | 335 | #SIMULATION 336 | #note that this is split for running a single trial in the nengo gui, and a full simulation 337 | 338 | 339 | #normalise stimuli to be between 0 and 180 degrees orientation 340 | def norm_p(p): 341 | if p<0: 342 | return 180+p 343 | if p>180: 344 | return p-180 345 | else: 346 | return p 347 | 348 | #Calculate normalised cosine similarity and avoid divide by 0 errors 349 | def cosine_sim(a,b): 350 | out=np.zeros(a.shape[0]) 351 | for i in range(0, a.shape[0]): 352 | if abs(np.linalg.norm(a[i])) > 0.05: 353 | out[i]=np.dot(a[i], b)/(np.linalg.norm(a[i])*np.linalg.norm(b)) 354 | return out 355 | 356 | 357 | 358 | if nengo_gui_on: 359 | load_gabors_svd = False #set to false for real simulation 360 | 361 | generate_gabors() #generate gabors 362 | create_model(seed=0) #build model 363 | 364 | memory_item_cued = 0 + 90 365 | probe_cued = 16 + 90 366 | memory_item_uncued = 0 + 90 367 | probe_uncued = 16 + 90 368 | 369 | soa = 500 370 | end_impulse = 2.65 371 | start_impulse = end_impulse - soa/1000 372 | 373 | else: #no gui 374 | 375 | #path 376 | cur_path = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))+'/data_exp3/' #store output in data subfolder 377 | 378 | #simulation 1: collect data for exp 3. 379 | if sim_to_run == 1: 380 | 381 | load_gabors_svd = False #set to false for real simulation 382 | 383 | n_subj = 20 384 | trials_per_subj = 280 385 | store_representations = False 386 | store_decisions = True 387 | uncued = False 388 | 389 | #np array to keep track of the input during the simulation runs 390 | initialangle_c = np.zeros(n_subj*trials_per_subj) #cued 391 | angle_index=0 392 | 393 | #orientation differences between probe and memory item for each run 394 | soalist=[0,50,100,250,500,0,50,100,250,500] 395 | angledif=[16,16,16,16,16,-16,-16,-16,-16,-16] 396 | 397 | for subj in range(n_subj): 398 | 399 | #create new gabor filters and model for each new participant 400 | generate_gabors() 401 | create_model(seed=subj) 402 | 403 | #use StpOCLsimulator to make use of the Nengo OCL implementation of STSP 404 | sim = StpOCLsimulator(network=model, seed=subj, context=context,progress_bar=False) 405 | 406 | #trials come in sets of 10, which we call a run (all possible soas, pos and neg 16), 407 | runs = int(trials_per_subj / 10) 408 | 409 | for run in range(runs): 410 | 411 | #run a trial with each possible orientation difference 412 | for cnt_in_run, soa in enumerate(soalist): 413 | 414 | print('Subject ' + str(subj+1) + '/' + str(n_subj) + '; Trial ' + str(run*10 + cnt_in_run + 1) + '/' + str(trials_per_subj)) 415 | 416 | #set probe and stim 417 | memory_item_cued=randint(0, 179) #random memory 418 | probe_cued=memory_item_cued+angledif[cnt_in_run] #probe based on that 419 | probe_cued=norm_p(probe_cued) #normalise probe 420 | 421 | #random phase 422 | or_memory_item_cued=memory_item_cued #original 423 | memory_item_cued=memory_item_cued+(180*randint(0, 9)) 424 | probe_cued=probe_cued+(180*randint(0, 9)) 425 | 426 | #store orientation 427 | initialangle_c[angle_index]=or_memory_item_cued 428 | 429 | #soa 430 | if soa == 0: 431 | end_impulse = -1 432 | else: 433 | end_impulse = 2.65 434 | start_impulse = end_impulse - soa/1000 435 | 436 | #run simulation 437 | sim.run(3) 438 | 439 | #store output 440 | np.savetxt(cur_path+sim_no+"_Diff_Theta_%i_Soa_%i_subj_%i_trial_%i.csv" % (angledif[cnt_in_run], soa, subj+1, run*10+cnt_in_run+1), sim.data[model.p_dec_cued][2500:2999,:], delimiter=",") 441 | 442 | #reset simulator, clean probes thoroughly 443 | sim.reset() 444 | for probe2 in sim.model.probes: 445 | del sim._probe_outputs[probe2][:] 446 | del sim.data 447 | sim.data = nengo.simulator.ProbeDict(sim._probe_outputs) 448 | 449 | angle_index=angle_index+1 450 | 451 | -------------------------------------------------------------------------------- /Model_sim_exp3.py.cfg: -------------------------------------------------------------------------------- 1 | _viz_0 = nengo_gui.components.SpaSimilarity(model.sensory_decode,args='default') 2 | _viz_config[_viz_0].max_value = 1 3 | _viz_config[_viz_0].min_value = -0.2 4 | _viz_config[_viz_0].show_pairs = False 5 | _viz_config[_viz_0].x = 0.04565717892260996 6 | _viz_config[_viz_0].y = 0.16705865099259368 7 | _viz_config[_viz_0].width = 0.07935918403792248 8 | _viz_config[_viz_0].height = 0.13926825209046173 9 | _viz_config[_viz_0].label_visible = True 10 | _viz_1 = nengo_gui.components.Raster(model.ensembles[1]) 11 | _viz_config[_viz_1].n_neurons = 1500 12 | _viz_config[_viz_1].x = 0.06656019285839798 13 | _viz_config[_viz_1].y = 1.150196449227392 14 | _viz_config[_viz_1].width = 0.07935918403792248 15 | _viz_config[_viz_1].height = 0.13926825209046173 16 | _viz_config[_viz_1].label_visible = True 17 | _viz_3 = nengo_gui.components.Value(model.ensembles[3]) 18 | _viz_config[_viz_3].max_value = 42 19 | _viz_config[_viz_3].min_value = -42 20 | _viz_config[_viz_3].show_legend = False 21 | _viz_config[_viz_3].legend_labels = ['label_0'] 22 | _viz_config[_viz_3].synapse = 0.01 23 | _viz_config[_viz_3].x = 0.9431318210358312 24 | _viz_config[_viz_3].y = 0.17436602452359595 25 | _viz_config[_viz_3].width = 0.1216628990759502 26 | _viz_config[_viz_3].height = 0.19412416693242154 27 | _viz_config[_viz_3].label_visible = True 28 | _viz_5 = nengo_gui.components.SpaSimilarity(model.memory_decode,args='default') 29 | _viz_config[_viz_5].max_value = 1 30 | _viz_config[_viz_5].min_value = -0.2 31 | _viz_config[_viz_5].show_pairs = False 32 | _viz_config[_viz_5].x = 0.6452121695402744 33 | _viz_config[_viz_5].y = 0.9346934126330544 34 | _viz_config[_viz_5].width = 0.07935918403792248 35 | _viz_config[_viz_5].height = 0.13926825209046173 36 | _viz_config[_viz_5].label_visible = True 37 | _viz_net_graph = nengo_gui.components.NetGraph() 38 | _viz_progress = nengo_gui.components.Progress() 39 | _viz_config[_viz_progress].x = 0 40 | _viz_config[_viz_progress].y = 0 41 | _viz_config[_viz_progress].width = 100 42 | _viz_config[_viz_progress].height = 100 43 | _viz_config[_viz_progress].label_visible = True 44 | _viz_sim_control = nengo_gui.components.SimControl() 45 | _viz_config[_viz_sim_control].shown_time = 0.5 46 | _viz_config[_viz_sim_control].kept_time = 4.0 47 | _viz_config[model].pos=(0.215376711776156, 0.11438011215863245) 48 | _viz_config[model].size=(0.6131842341444002, 0.6131842341444002) 49 | _viz_config[model].expanded=True 50 | _viz_config[model].has_layout=True 51 | _viz_config[model.ensembles[0]].pos=(0.33041123391021066, 0.48315238372474056) 52 | _viz_config[model.ensembles[0]].size=(0.053763440860215055, 0.1020408163265306) 53 | _viz_config[model.ensembles[1]].pos=(0.4818870288654669, 0.8044761538057824) 54 | _viz_config[model.ensembles[1]].size=(0.053763440860215055, 0.1020408163265306) 55 | _viz_config[model.ensembles[2]].pos=(0.6054048905297307, 0.485000237818829) 56 | _viz_config[model.ensembles[2]].size=(0.053763440860215055, 0.1020408163265306) 57 | _viz_config[model.ensembles[3]].pos=(0.8321523209122026, 0.48510631596044274) 58 | _viz_config[model.ensembles[3]].size=(0.053763440860215055, 0.1020408163265306) 59 | _viz_config[model.memory_decode].pos=(0.48406219827730346, 1.1842807378985376) 60 | _viz_config[model.memory_decode].size=(0.07199178797724101, 0.11543568040366908) 61 | _viz_config[model.memory_decode].expanded=False 62 | _viz_config[model.memory_decode].has_layout=False 63 | _viz_config[model.memory_decode.state_ensembles].expanded=False 64 | _viz_config[model.memory_decode.state_ensembles].has_layout=False 65 | _viz_config[model.nodes[0]].pos=(0.06961257909962328, 0.48110559131271463) 66 | _viz_config[model.nodes[0]].size=(0.043010752688172046, 0.08163265306122448) 67 | _viz_config[model.nodes[1]].pos=(0.06989247311827958, 0.7959183673469387) 68 | _viz_config[model.nodes[1]].size=(0.043010752688172046, 0.08163265306122448) 69 | _viz_config[model.sensory_decode].pos=(0.33258900053375823, 0.06822915683220981) 70 | _viz_config[model.sensory_decode].size=(0.07711293532218821, 0.08724346412454488) 71 | _viz_config[model.sensory_decode].expanded=False 72 | _viz_config[model.sensory_decode].has_layout=False 73 | _viz_config[model.sensory_decode.state_ensembles].expanded=False 74 | _viz_config[model.sensory_decode.state_ensembles].has_layout=False -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # STSP 2 | A Nengo Implementation of Short-term Synaptic Plasticity (STSP) as proposed by Mongillo, Barak and Tsodyks (2008) 3 | ## How to use it 4 | In order to import the necessary classes/functions use: 5 | 6 | ```from stp_ocl_implementation import *``` 7 | 8 | In order to use Spiking leaky integrate-and-fire implementing STSP, specify the neuron type of an ensemble as follows: 9 | 10 | ```neuron_type=stpLIF()``` 11 | 12 | And specify the following learning rule for outgoing connections: 13 | 14 | ```learning_rule_type=STP()``` 15 | 16 | To use the OpenCL implementation use the following simulator: 17 | 18 | ```StpOCLsimulator()``` 19 | 20 | The following additional probes can be used: 21 | 22 | ```nengo.Probe(ensemble.neurons, 'calcium')``` 23 | ```nengo.Probe(ensemble.neurons, 'resources') ``` 24 | 25 | ## Example models/simulations 26 | The implementation of STSP was used to create a functional spiking neuron model of working memory: https://www.biorxiv.org/content/10.1101/823559v3. Using this mechanism, the model is able to maintain information in activity-silent states. This model was used to simulate three working memory tasks (the Model_sim_exp.py files), earlier performed by human participants (Wolff et al. 2017). Both the model's behavior as well as its neural representations are in agreement with the human data. 27 | 28 | ## Theoretical background 29 | Synaptic efficiency is based on two parameters: the amount of available resources to the presynaptic neuron (x, normalised to be between 0 and 1) and the fraction of resources used each time a neuron fires (u), reflecting the residual presynaptic calcium level. 30 | 31 | For all LIF neurons to which we want to apply STSP, every simulation time step u and x are calculated according to equation 2.1 and 2.2, respectively. When a neuron fires, its resources x are decreased by u x, mimicking neurotransmitter depletion. At the same time, its calcium level u is increased, mimicking calcium influx into the presynaptic terminal. Both u and x relax back to baseline with time constants 𝜏_𝐷 (0.2s) and 𝜏_𝐹 (1.5s), respectively. The mechanisms are described by: 32 | 33 | 𝑑𝑥/𝑑𝑡= (1−𝑥)/𝜏_𝐷 − 𝑢 𝑥 𝛿(𝑡−𝑡_𝑠𝑝) (2.1) 34 | 35 | 𝑑𝑢/𝑑𝑡= (𝑈−𝑢)/𝜏_𝐹 − 𝑈 (1−𝑢) 𝛿(𝑡−𝑡_𝑠𝑝) (2.2) 36 | 37 | Where x represents the available resources, u represents the residual calcium level and U its baseline level, 𝜏_𝐹 is the facilitating time constant and 𝜏_𝐷 the depressing time constant, 𝛿 represents the Dirac delta function, t the simulation time and t_sp the time of a presynaptic spike. 38 | 39 | Outgoing connection weights of neurons implementing STSP are determined by both their initial connection weight and their current synaptic efficiency. Initial connections weights are calculated by the NEF, while synaptic efficiency is set to the product of the current value of u and x of the presynaptic neuron, normalised by their baseline value (equation 2.3). This results in a system where after a neuron fires its outgoing connections will be depressed on the time scale of 𝜏_𝐷 and facilitated on the timescale of 𝜏_𝐹. 40 | 41 | 𝑑𝑤_𝑖𝑗/𝑑𝑡= (c 𝑢)/𝐶 𝑤_0𝑖𝑗 (2.3) 42 | 43 | Where 𝑤_𝑖𝑗 represents the connection weight between neuron i and j and 𝑤_0𝑖𝑗 the initial connection weight between neuron i and j. 44 | 45 | ## Sources 46 | Mongillo G, Barak O, Tsodyks M. Synaptic Theory of Working Memory. Science. 2008;319: 1543–1546. doi:10.1126/science.1150769 47 | 48 | Wolff MJ, Jochim J, Akyürek EG, Stokes MG. Dynamic hidden states underlying working-memory-guided behavior. Nat Neuroscience. 2017;20: 864–871. doi:10.1038/nn.4546 49 | 50 | -------------------------------------------------------------------------------- /generate_stimuli.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | from PIL import Image 3 | from random import randint 4 | import psychopy.visual 5 | import psychopy.event 6 | import numpy as np 7 | #Create png images using Psychopy 8 | 9 | 10 | pixels=128 #width and height of images 11 | 12 | diameter=pixels 13 | spatialfr=4.385/pixels 14 | angles=np.arange(-90,91,1) 15 | phases=np.arange(0,1,.1) 16 | win = psychopy.visual.Window( 17 | size=[diameter, diameter], 18 | units="pix", 19 | fullscr=False, 20 | color=(0,0,0) 21 | ) 22 | 23 | grating = psychopy.visual.GratingStim( 24 | win=win, 25 | units="pix", 26 | size=[diameter, diameter] 27 | ) 28 | 29 | grating.sf = spatialfr 30 | 31 | grating.mask = "circle" 32 | 33 | grating.contrast = 1 34 | 35 | #generate grating stimuli 36 | for angle in angles: 37 | for phase in phases: 38 | name="stim"+str(angle)+"_"+str(round(phase,1))+".png" 39 | grating.ori = angle 40 | grating.phase = phase 41 | grating.draw() 42 | win.flip() 43 | win.getMovieFrame() 44 | win.saveMovieFrames('Stimuli/' + name) 45 | 46 | grating.contrast = 1 47 | 48 | #generate ping 49 | grating = psychopy.visual.RadialStim(win=win, mask='circle', size=[diameter, diameter]) 50 | grating.setAngularCycles(0) 51 | grating.sf = spatialfr 52 | name="stim999"+".png" 53 | grating.draw() 54 | win.flip() 55 | win.getMovieFrame() 56 | win.saveMovieFrames('Stimuli/' + name) 57 | 58 | 59 | win.close() 60 | -------------------------------------------------------------------------------- /stp_ocl_implementation.py: -------------------------------------------------------------------------------- 1 | #imports 2 | from nengo.exceptions import SimulationError, ValidationError, BuildError 3 | from nengo.neurons import LIF, LIFRate 4 | from nengo.builder import Builder, Operator, Signal 5 | from nengo.builder.neurons import SimNeurons 6 | from nengo.learning_rules import * 7 | from nengo.builder.learning_rules import * 8 | from nengo.params import (NumberParam) 9 | from nengo.utils.compat import is_iterable, is_string, itervalues, range 10 | from nengo.builder.operator import DotInc, ElementwiseInc, Copy, Reset 11 | from nengo.connection import LearningRule 12 | from nengo.ensemble import Ensemble, Neurons 13 | from nengo_ocl import Simulator 14 | from nengo_ocl.utils import as_ascii #, indent, round_up 15 | from mako.template import Template 16 | import pyopencl as cl 17 | from nengo_ocl.plan import Plan 18 | from nengo_ocl.clra_nonlinearities import _plan_template 19 | from collections import OrderedDict 20 | import nengo.dists as nengod 21 | from nengo_ocl.raggedarray import RaggedArray 22 | from nengo_ocl.clraggedarray import CLRaggedArray, to_device 23 | from nengo.dists import Uniform 24 | 25 | 26 | #create new neuron type stpLIF with resources (x) and calcium (u) 27 | 28 | class stpLIF(LIF): 29 | probeable = ('spikes', 'resources', 'voltage', 'refractory_time', 'calcium') 30 | 31 | tau_x = NumberParam('tau_x', low=0, low_open=True) 32 | tau_u = NumberParam('tau_u', low=0, low_open=True) 33 | U = NumberParam('U', low=0, low_open=True) 34 | 35 | def __init__(self, tau_x=0.2, tau_u=1.5, U=0.2, **lif_args): 36 | super(stpLIF, self).__init__(**lif_args) 37 | self.tau_x = tau_x 38 | self.tau_u = tau_u 39 | self.U = U 40 | 41 | @property 42 | def _argreprs(self): 43 | args = super(LIFRate, self)._argreprs 44 | if self.tau_x != 0.2: 45 | args.append("tau_x=%s" % self.tau_x) 46 | if self.tau_u != 1.5: 47 | args.append("tau_u=%s" % self.tau_u) 48 | if self.U!= 0.2: 49 | args.append("U=%s" % self.U) 50 | return args 51 | 52 | def step_math(self, dt, J, output, voltage, ref, resources, calcium): 53 | """Implement the u and x parameters """ 54 | x = resources 55 | u = calcium 56 | LIF.step_math(self, dt, J, output, voltage, ref) 57 | 58 | #calculate u and x 59 | dx=dt * ( (1-x)/self.tau_x - u*x*output ) 60 | du=dt * ( (self.U-u)/self.tau_u + self.U*(1-u)*output ) 61 | 62 | x += dx 63 | u += du 64 | 65 | 66 | 67 | #add builder for stpLIF 68 | 69 | @Builder.register(stpLIF) 70 | def build_stpLIF(model, stplif, neurons): 71 | 72 | model.sig[neurons]['voltage'] = Signal( 73 | np.zeros(neurons.size_in), name="%s.voltage" % neurons) 74 | model.sig[neurons]['refractory_time'] = Signal( 75 | np.zeros(neurons.size_in), name="%s.refractory_time" % neurons) 76 | model.sig[neurons]['resources'] = Signal( 77 | np.ones(neurons.size_in), name="%s.resources" % neurons) 78 | model.sig[neurons]['calcium'] = Signal( 79 | np.full(neurons.size_in, stplif.U), name="%s.calcium" % neurons) 80 | model.add_op(SimNeurons(neurons=stplif, 81 | J=model.sig[neurons]['in'], 82 | output=model.sig[neurons]['out'], 83 | states=[model.sig[neurons]['voltage'], 84 | model.sig[neurons]['refractory_time'], 85 | model.sig[neurons]['resources'], 86 | model.sig[neurons]['calcium']])) 87 | 88 | 89 | 90 | #create new learning rule to model short term plasticity (only works if pre-ensemble has neuron type StpLIF) 91 | 92 | class STP(LearningRuleType): 93 | """STP learning rule. 94 | Modifies connection weights according to the calcium and resources of the neuron presynaptic 95 | """ 96 | modifies = 'weights' 97 | probeable = ('delta', 'calcium', 'resources') 98 | 99 | def __init__(self): 100 | super(STP, self).__init__(size_in=0) 101 | 102 | 103 | 104 | 105 | #builders for STP 106 | class SimSTP(Operator): 107 | r"""Calculate connection weight change according to the STP rule. 108 | Implements the STP learning rule of the form: 109 | .. math:: omega_{ij} = ((u_i * x_i) / U_i) * omega_{ij-initial} 110 | where 111 | * :math:`\omega_{ij}` is the connection weight between the two neurons. 112 | * :math:`u_i` is the calcium level of the presynaptic neuron. 113 | * :math:`x_i` is the resources level of the presynaptic neuron. 114 | * :math:`U_i` is the baseline calcium level of the presynaptic neuron. 115 | * :math:`\omega_{ij-initial}` is the initial connection weight between the two neurons. 116 | Parameters 117 | ---------- 118 | weights : Signal 119 | The connection weight matrix, :math:`\omega_{ij}`. 120 | delta : Signal 121 | The synaptic weight change to be applied, :math:`\Delta ((u_i * x_i) / U_i) * initial_omega_{ij} - omega_{ij}`. 122 | calcium : Signal 123 | The calcium level of the presynaptic neuron, :math:`u_i`. 124 | resources : Signal 125 | The resources level of the presynaptic neuron, :math:`x_i`. 126 | tag : str, optional (Default: None) 127 | A label associated with the operator, for debugging purposes. 128 | Attributes 129 | ---------- 130 | delta : Signal 131 | The synaptic weight change to be applied, :math:`\Delta \omega_{ij}`. 132 | calcium : Signal 133 | The calcium level of the presynaptic neuron, :math:`u_i`. 134 | resources : Signal 135 | The resources level of the presynaptic neuron, :math:`x_i`. 136 | tag : str or None 137 | A label associated with the operator, for debugging purposes. 138 | weights : Signal 139 | The connection weight matrix, :math:`\omega_{ij}`. 140 | Notes 141 | ----- 142 | 1. sets ``[]`` 143 | 2. incs ``[]`` 144 | 3. reads ``[weights, calcium, resources]`` 145 | 4. updates ``[delta]`` 146 | """ 147 | 148 | def __init__(self, calcium, resources, weights, delta, 149 | tag=None): 150 | super(SimSTP, self).__init__(tag=tag) 151 | self.sets = [] 152 | self.incs = [] 153 | self.reads = [weights, calcium, resources] 154 | self.updates = [delta] 155 | 156 | @property 157 | def delta(self): 158 | return self.updates[0] 159 | 160 | @property 161 | def weights(self): 162 | return self.reads[0] 163 | 164 | @property 165 | def calcium(self): 166 | return self.reads[1] 167 | 168 | @property 169 | def resources(self): 170 | return self.reads[2] 171 | 172 | 173 | 174 | 175 | def _descstr(self): 176 | return '%s' % (self.delta) 177 | 178 | def make_step(self, signals, dt, rng): 179 | weights = signals[self.weights] 180 | delta = signals[self.delta] 181 | init_weights = self.weights.initial_value 182 | calcium = signals[self.calcium] 183 | resources = signals[self.resources] 184 | U=self.calcium.initial_value 185 | def step_simstp(): 186 | # perform update 187 | delta[...] = ((calcium * resources)/U) * init_weights - weights 188 | 189 | return step_simstp 190 | 191 | @Builder.register(STP) 192 | def build_stp(model, stp, rule): 193 | """Builds a `.STP` object into a model. 194 | 195 | Parameters 196 | ---------- 197 | model : Model 198 | The model to build into. 199 | stp : STP 200 | Learning rule type to build. 201 | rule : LearningRule 202 | The learning rule object corresponding to the neuron type. 203 | Notes 204 | ----- 205 | Does not modify ``model.params[]`` and can therefore be called 206 | more than once with the same `.STP` instance. 207 | """ 208 | 209 | conn = rule.connection 210 | calcium = model.sig[get_pre_ens(conn).neurons]['calcium'] 211 | resources = model.sig[get_pre_ens(conn).neurons]['resources'] 212 | 213 | model.add_op(SimSTP(calcium, 214 | resources, 215 | model.sig[conn]['weights'], 216 | model.sig[rule]['delta'], 217 | )) 218 | 219 | # expose these for probes 220 | model.sig[rule]['calcium'] = calcium 221 | model.sig[rule]['resources'] = resources 222 | 223 | 224 | #----- Nengo OCL implementation of STP and StpLIF ------ 225 | #------------------------------------------------------- 226 | 227 | 228 | def plan_stp(queue, calcium, resources, weights, delta, init_weights, init_calcium, tag=None): 229 | assert (len(calcium) == len(resources) == len(weights) == len(delta) == 230 | len(init_weights) == init_calcium.size) 231 | N = len(calcium) 232 | 233 | for arr in (calcium, resources): # vectors 234 | assert (arr.shape1s == 1).all() 235 | for arr in (delta, weights, init_weights): # matrices 236 | assert (arr.stride1s == 1).all() 237 | 238 | #assert (resources.shape0s == weights.shape0s).all() 239 | #assert (calcium.shape0s == weights.shape1s).all() 240 | assert (weights.shape0s == delta.shape0s).all() 241 | assert (weights.shape1s == delta.shape1s).all() 242 | assert (weights.shape0s == init_weights.shape0s).all() 243 | assert (weights.shape1s == init_weights.shape1s).all() 244 | 245 | assert (calcium.ctype == resources.ctype == weights.ctype == delta.ctype == 246 | init_weights.ctype == init_calcium.ctype) 247 | 248 | text = """ 249 | __kernel void stp( 250 | __global const int *shape0s, 251 | __global const int *shape1s, 252 | __global const int *calcium_stride0s, 253 | __global const int *calcium_starts, 254 | __global const ${type} *calcium_data, 255 | __global const int *resources_stride0s, 256 | __global const int *resources_starts, 257 | __global const ${type} *resources_data, 258 | __global const int *weights_stride0s, 259 | __global const int *weights_starts, 260 | __global const ${type} *weights_data, 261 | __global const int *delta_stride0s, 262 | __global const int *delta_starts, 263 | __global ${type} *delta_data, 264 | __global const int *init_weights_stride0s, 265 | __global const int *init_weights_starts, 266 | __global const ${type} *init_weights_data, 267 | __global const ${type} *init_calciums 268 | 269 | 270 | ) 271 | { 272 | const int ij = get_global_id(0); 273 | const int k = get_global_id(1); 274 | const int shape0 = shape0s[k]; 275 | const int shape1 = shape1s[k]; 276 | const int i = ij / shape1; 277 | const int j = ij % shape1; 278 | __global ${type} *delta = delta_data + delta_starts[k]; 279 | const ${type} calcium = calcium_data[calcium_starts[k] + i*calcium_stride0s[k]]; 280 | const ${type} resources = resources_data[resources_starts[k] + i*resources_stride0s[k]]; 281 | const ${type} weight = weights_data[ 282 | weights_starts[k] + i*weights_stride0s[k]+j]; 283 | const ${type} init_weights = init_weights_data[init_weights_starts[k] + i*init_weights_stride0s[k]+j]; 284 | const ${type} init_calcium = init_calciums[k]; 285 | 286 | if (i < shape0) { 287 | delta[i*delta_stride0s[k] + j] = 288 | ((calcium*resources/init_calcium)*init_weights)-weight; 289 | } 290 | } 291 | """ 292 | 293 | textconf = dict(type=calcium.ctype) 294 | text = as_ascii(Template(text, output_encoding='ascii').render(**textconf)) 295 | 296 | full_args = ( 297 | delta.cl_shape0s, delta.cl_shape1s, 298 | calcium.cl_stride0s, calcium.cl_starts, calcium.cl_buf, 299 | resources.cl_stride0s, resources.cl_starts, resources.cl_buf, 300 | weights.cl_stride0s, weights.cl_starts, weights.cl_buf, 301 | delta.cl_stride0s, delta.cl_starts, delta.cl_buf, 302 | init_weights.cl_stride0s, init_weights.cl_starts, init_weights.cl_buf, 303 | init_calcium, 304 | ) 305 | _fn = cl.Program(queue.context, text).build().stp 306 | _fn.set_args(*[arr.data for arr in full_args]) 307 | 308 | lsize = None 309 | gsize = (delta.sizes.max(), N) 310 | plan = Plan(queue, _fn, gsize, lsize=lsize, name="cl_stp", tag=tag) 311 | plan.full_args = full_args # prevent garbage-collection 312 | plan.flops_per_call = 6 * delta.sizes.sum() 313 | plan.bw_per_call = (calcium.nbytes + resources.nbytes + weights.nbytes + 314 | delta.nbytes + init_weights.nbytes + init_calcium.nbytes) 315 | return plan 316 | 317 | 318 | 319 | def plan_stplif(queue, dt, J, V, W, outS, ref, tau, amp, u, x, tau_u, tau_x, U, upsample=1, **kwargs): 320 | assert J.ctype == 'float' 321 | for x in [V, W, outS, u, x]: 322 | assert x.ctype == J.ctype 323 | 324 | inputs = dict(J=J, V=V, W=W, x=x, u=u) 325 | outputs = dict(outV=V, outW=W, outS=outS, outx=x, outu=u ) 326 | parameters = dict(tau=tau, ref=ref, amp=amp, tau_x=tau_x, tau_u=tau_u, U=U) 327 | 328 | dt = float(dt) 329 | textconf = dict( 330 | type=J.ctype, dt=dt, upsample=upsample, 331 | dtu=dt/upsample, dtu_inv=upsample/dt, dt_inv=1/dt) 332 | decs = """ 333 | char spiked; 334 | ${type} dV; 335 | const ${type} V_threshold = 1; 336 | const ${type} dtu = ${dtu}, dtu_inv = ${dtu_inv}, dt_inv = ${dt_inv}; 337 | ${type} delta_t; 338 | const ${type} dt = ${dt}; 339 | """ 340 | # TODO: could precompute -expm1(-dtu / tau) 341 | text = """ 342 | spiked = 0; 343 | % for ii in range(upsample): 344 | W -= dtu; 345 | delta_t = (W > dtu) ? 0 : (W < 0) ? dtu : dtu - W; 346 | dV = -expm1(-delta_t / tau) * (J - V); 347 | V += dV; 348 | if (V > V_threshold) { 349 | const ${type} t_spike = dtu + tau * log1p( 350 | -(V - V_threshold) / (J - V_threshold)); 351 | W = ref + t_spike; 352 | V = 0; 353 | spiked = 1; 354 | }else if (V < 0) { 355 | V = 0; 356 | } 357 | % endfor 358 | outV = V; 359 | outW = W; 360 | outS = (spiked) ? amp*dt_inv : 0; 361 | outx = x+ dt* ((1-x)/tau_x - u*x*outS); 362 | outu = u+ dt* ((U-u)/tau_u + U*(1-u)*outS) ; 363 | """ 364 | decs = as_ascii(Template(decs, output_encoding='ascii').render(**textconf)) 365 | text = as_ascii(Template(text, output_encoding='ascii').render(**textconf)) 366 | cl_name = "cl_stplif" 367 | return _plan_template( 368 | queue, cl_name, text, declares=decs, 369 | inputs=inputs, outputs=outputs, parameters=parameters, **kwargs) 370 | 371 | 372 | class StpOCLsimulator(Simulator): 373 | 374 | def _plan_stpLIF(self, ops): 375 | if not all(op.neurons.min_voltage == 0 for op in ops): 376 | raise NotImplementedError("LIF min voltage") 377 | dt = self.model.dt 378 | J = self.all_data[[self.sidx[op.J] for op in ops]] 379 | V = self.all_data[[self.sidx[op.states[0]] for op in ops]] 380 | W = self.all_data[[self.sidx[op.states[1]] for op in ops]] 381 | x = self.all_data[[self.sidx[op.states[2]] for op in ops]] 382 | u = self.all_data[[self.sidx[op.states[3]] for op in ops]] 383 | S = self.all_data[[self.sidx[op.output] for op in ops]] 384 | ref = self.RaggedArray([op.neurons.tau_ref * np.ones(op.J.size) 385 | for op in ops], dtype=J.dtype) 386 | tau = self.RaggedArray([op.neurons.tau_rc * np.ones(op.J.size) 387 | for op in ops], dtype=J.dtype) 388 | tau_x = self.RaggedArray([op.neurons.tau_x * np.ones(op.J.size) 389 | for op in ops], dtype=J.dtype) 390 | tau_u = self.RaggedArray([op.neurons.tau_u * np.ones(op.J.size) 391 | for op in ops], dtype=J.dtype) 392 | U = self.RaggedArray([op.neurons.U * np.ones(op.J.size) 393 | for op in ops], dtype=J.dtype) 394 | amp = self.RaggedArray([op.neurons.amplitude * np.ones(op.J.size) 395 | for op in ops], dtype=J.dtype) 396 | 397 | return [plan_stplif(self.queue, dt, J, V, W, S, ref, tau, amp, u, x, tau_u, tau_x, U)] 398 | 399 | 400 | def plan_SimSTP(self, ops): 401 | calcium = self.all_data[[self.sidx[op.calcium] for op in ops]] 402 | resources = self.all_data[[self.sidx[op.resources] for op in ops]] 403 | weights = self.all_data[[self.sidx[op.weights] for op in ops]] 404 | delta = self.all_data[[self.sidx[op.delta] for op in ops]] 405 | init_weights = self.RaggedArray([op.weights.initial_value for op in ops], dtype=calcium.dtype) 406 | init_calcium = self.Array([op.calcium.initial_value[0] for op in ops]) 407 | 408 | return [plan_stp(self.queue, calcium, resources, weights, delta, init_weights, init_calcium)] --------------------------------------------------------------------------------