From 6bd8f92ee359e283fdd8b4a08ad15357b944eeba Mon Sep 17 00:00:00 2001 From: KOBAYASHI Takeshi Date: Mon, 13 Jun 2022 17:58:04 +0900 Subject: [PATCH 01/19] spinnaker version 7 to 8 (not tested) --- examples/cmp_australia.py | 72 +- examples/cmp_world.py | 279 ++- examples/run.sh | 2 +- examples/spin_lattice.py | 104 +- examples/sudoku.py | 68 +- spinnaker_csp/__init__.py | 43 +- spinnaker_csp/analysis.py | 529 ++++-- spinnaker_csp/puzzles/sudoku_puzzles.py | 220 ++- spinnaker_csp/snn_creator.py | 684 ++++--- spinnaker_csp/translators/spin2csp.py | 61 +- spinnaker_csp/translators/sudoku2csp.py | 63 +- .../translators/world_bordering_countries.py | 1628 +++++++++-------- 12 files changed, 2240 insertions(+), 1513 deletions(-) mode change 100644 => 100755 examples/run.sh diff --git a/examples/cmp_australia.py b/examples/cmp_australia.py index f61b930..5250114 100644 --- a/examples/cmp_australia.py +++ b/examples/cmp_australia.py @@ -3,7 +3,7 @@ # # "Using Stochastic Spiking Neural Networks on SpiNNaker to Solve Constraint Satisfaction Problems" # Submitted to the journal Frontiers in Neuroscience| Neuromorphic Engineering -#----------------------------------------------------------------------------------------------------------------------- +# ----------------------------------------------------------------------------------------------------------------------- """ Coloring map problem for the map of australia. @@ -23,63 +23,75 @@ The coloring uses 3 colors red -> 0, green->1, blue->2 """ -import spynnaker7.pyNN as p # simulator +import spynnaker8 as p # simulator import argparse + try: from spinnaker_csp import CSP, plot_entropy except: import sys import os - sys.path.append(os.getcwd()+'/..') + + sys.path.append(os.getcwd() + "/..") from spinnaker_csp import CSP, plot_entropy -parser = argparse.ArgumentParser(description='''This script creates a spiking neural network representation of the +parser = argparse.ArgumentParser( + description="""This script creates a spiking neural network representation of the problem of 3-colouring the map of australia whose dynamics implements a - stochastic search for satisfiability.''') -parser.add_argument("--i_offset", help='offset current', type=float, default=0.7) -parser.add_argument("--phase", help='phase shift for depression', type=float, default=100) -parser.add_argument("--w_inh", help='inhibition weight for depression', type=float, default=-1.5) -parser.add_argument("-n", "--name", help='name to use for naming files', type=str, default='world_cmp') + stochastic search for satisfiability.""" +) +parser.add_argument("--i_offset", help="offset current", type=float, default=0.7) +parser.add_argument( + "--phase", help="phase shift for depression", type=float, default=100 +) +parser.add_argument( + "--w_inh", help="inhibition weight for depression", type=float, default=-1.5 +) +parser.add_argument( + "-n", "--name", help="name to use for naming files", type=str, default="world_cmp" +) args = parser.parse_args() name = args.name -bordering_states = [{'source':1, 'target':2}, - {'source':1, 'target':6}, - {'source':2, 'target':3}, - {'source':2, 'target':6}, - {'source':3, 'target':4}, - {'source':3, 'target':6}, - {'source':4, 'target':5}, - {'source':4, 'target':6}, - {'source':5, 'target':6}] +bordering_states = [ + {"source": 1, "target": 2}, + {"source": 1, "target": 6}, + {"source": 2, "target": 3}, + {"source": 2, "target": 6}, + {"source": 3, "target": 4}, + {"source": 3, "target": 6}, + {"source": 4, "target": 5}, + {"source": 4, "target": 6}, + {"source": 5, "target": 6}, +] # SpiNNaker setup. run_time = 30000 p.setup(timestep=1.0) # Build spiking neural network. -map = CSP(variables_number = 7, domain_size = 3, constraints=bordering_states, core_size=10) -map.cell_params_lif['i_offset']= 0.2 -map.clues_inhibition=False +map = CSP(variables_number=7, domain_size=3, constraints=bordering_states, core_size=10) +map.cell_params_lif["i_offset"] = 0.2 +map.clues_inhibition = False map.build_domains_pops() map.build_stimulation_pops(1, stim_ratio=1, full=True) map.build_dissipation_pops() -map.internal_inhibition(w_range=[-1.2, -1.5], d_range=[1.0,1.0]) -map.stimulate_cores(w_range=[0.05, 0.3], d_range=[1.0,1.0]) -map.depress_cores(w_range=[-1.5, -2.0], d_range=[1.0,1.0]) -map.apply_constraints(w_range=[ 1.2, 1.4], d_range=[1.0,1.2]) +map.internal_inhibition(w_range=[-1.2, -1.5], d_range=[1.0, 1.0]) +map.stimulate_cores(w_range=[0.05, 0.3], d_range=[1.0, 1.0]) +map.depress_cores(w_range=[-1.5, -2.0], d_range=[1.0, 1.0]) +map.apply_constraints(w_range=[1.2, 1.4], d_range=[1.0, 1.2]) # Record spikes from variable populations. map.recording() # Run simulation. p.run(run_time) # Save recorded spikes. -map.save('australia') -map.report_network_params('report_cmp_australia') +map.save("australia") +map.report_network_params("report_cmp_australia") # End simulation. p.end() # Plot entropy. -sol=plot_entropy('australia', 200, preprint=False) +sol = plot_entropy("australia", 200, preprint=False) formatted = [] for i in sol: - formatted.append('{{%d}}'%(i+1)) -print "".join(formatted) \ No newline at end of file + formatted.append("{{%d}}" % (i + 1)) +print("".join(formatted)) diff --git a/examples/cmp_world.py b/examples/cmp_world.py index 4f582b4..c473bc3 100644 --- a/examples/cmp_world.py +++ b/examples/cmp_world.py @@ -1,10 +1,9 @@ - # This module has been developed by Gabriel Fonseca and Steve Furber at The University of Manchester as part of the # paper: # # "Using Stochastic Spiking Neural Networks on SpiNNaker to Solve Constraint Satisfaction Problems" # Submitted to the journal Frontiers in Neuroscience| Neuromorphic Engineering -#----------------------------------------------------------------------------------------------------------------------- +# ----------------------------------------------------------------------------------------------------------------------- """ Spiking Neural Network Solver for the problem of 4-colouring the map of the world. @@ -21,59 +20,204 @@ The countries and bordering correspond to data from the United Nations available in Mathematica Wolfram (Wolfram Research, 2017). """ -import spynnaker7.pyNN as p # simulator +import spynnaker8 as p # simulator import argparse + try: from spinnaker_csp import CSP, plot_entropy, world_borders except: import sys import os - sys.path.append(os.getcwd()+'/..') + + sys.path.append(os.getcwd() + "/..") from spinnaker_csp import CSP, plot_entropy, world_borders -parser = argparse.ArgumentParser(description='''This script creates a spiking neural network representation of the +parser = argparse.ArgumentParser( + description="""This script creates a spiking neural network representation of the problem of 4-colouring the map of the world whose dynamics implements a - stochastic search for satisfiability.''') -parser.add_argument( '--tau_refrac', type=float, help = 'refractory period', default=2.0) -parser.add_argument( '--name', type=str, help = 'sufix to name files', default='cmp_world') -parser.add_argument( '--i_offset', type=float, help = 'constant current stimulation', default=0.0) -parser.add_argument( '--diss_shrink', type=float, help = 'shrinkage of the time to activate/deactivate diss populations', default=1.0) -parser.add_argument( '--stim_pops', type=int, help = 'number of noise (Poisson) stimulation populations', default=1) -parser.add_argument( '--internal_delay_min', type=float, help = 'min delay for internal inhibition synapses', default=2.0) -parser.add_argument( '--internal_max', type=float, help = 'max value for internal inhibition weights', default=-2.4) -parser.add_argument( '--diss_full', type=bool, help = 'whether the dissipation should last for the full simulation', default=False) -parser.add_argument( '--tau_syn_E', type=float, help = 'excitatory synapse time constant', default=5.0) -parser.add_argument( '--stim_full', type=bool, help = 'whether the stimulation should last for the full simulation', default=False) -parser.add_argument( '--diss_max', type=float, help = 'max value for dissipation weights', default=-1.5) -parser.add_argument( '--cm', type=float, help = 'membrane capacitance', default=0.25) -parser.add_argument( '--internal_delay_max', type=float, help = 'max delay for internal inhibition synapses', default=2.0) -parser.add_argument( '--stim_phase', type=int, help = 'a delay for activation of stimulation after the simulation begins', default=0) -parser.add_argument( '--diss_pops', type=int, help = 'number of noise (Poisson) dissipation populations', default=1) -parser.add_argument( '--diss_ratio', type=float, help = 'fraction of the dissipation interval to keep dissipation active', default=1.0) -parser.add_argument( '--stim_delay_min', type=float, help = 'min value of the synaptic delay from the stimulating poisson sources to the principal neurons', default=1.0) -parser.add_argument( '--stim_shrink', type=float, help = 'shrinkage of the time to activate/deactivate stim populations', default=1.0) -parser.add_argument( '--v_rest', type=float, help = 'rest membrane potential', default=-65.0) -parser.add_argument( '--activate_stim', type=bool, help = 'whether stimulation with Poisson sources should be activated', default=True) -parser.add_argument( '--stim_delay_max', type=float, help = 'max value of the synaptic delay from the stimulating poisson sources to the principal neurons', default=1.0) -parser.add_argument( '--stim_max', type=float, help = 'max value for stimulation weights', default=1.5) -parser.add_argument( '--internal_min', type=float, help = 'min value for internal inhibition weights', default=-2.1) -parser.add_argument( '--stim_ratio', type=float, help = 'fraction of the stimulation interval to keep stimulation active', default=1.0) -parser.add_argument( '--diss_phase', type=int, help = 'a delay for activation of dissipation after the simulation begins', default=0) -parser.add_argument( '--cons_max', type=float, help = 'max value for constraints inhibition weights', default=-1.0) -parser.add_argument( '--activate_diss', type=bool, help = 'whether stimulation with Poisson sources should be activated', default=True) -parser.add_argument( '--stim_min', type=float, help = 'min value for stimulation weights', default=0.1) -parser.add_argument( '--cons_min', type=float, help = 'min value for constraints inhibition weights', default=-0.5) -parser.add_argument( '--diss_delay_min', type=float, help = 'min value of the synaptic delay from the dissipating poisson sources to the principal neurons', default=1.0) -parser.add_argument( '--v_reset', type=float, help = 'reset membrane potential', default=-70.0) -parser.add_argument( '--plot_dynamics', type=bool, help = 'whether plot dynamics or not', default=False) -parser.add_argument( '--tau_syn_I', type=float, help = 'inhibitory synapse time constant', default=5.0) -parser.add_argument( '--clues_inh', type=bool, help = 'whether allow inhibition over clues', default=True) -parser.add_argument( '--diss_min', type=float, help = 'min value for dissipation weights', default=-1.5) -parser.add_argument( '--diss_delay_max', type=float, help = 'max value of the synaptic delay from the dissipating poisson sources to the principal neurons', default=1.0) -parser.add_argument( '--tau_m', type=float, help = 'membrane time constant', default=20.0) -parser.add_argument( '--run_time', type=int, help = 'duration of the simulation', default=500000) -parser.add_argument( '--v_thresh', type=float, help = 'firing threshold voltage', default=-50.0) -parser.add_argument( '--core_size', type=int, help = 'number of neurons per domain in a variable', default=42) + stochastic search for satisfiability.""" +) +parser.add_argument("--tau_refrac", type=float, help="refractory period", default=2.0) +parser.add_argument("--name", type=str, help="sufix to name files", default="cmp_world") +parser.add_argument( + "--i_offset", type=float, help="constant current stimulation", default=0.0 +) +parser.add_argument( + "--diss_shrink", + type=float, + help="shrinkage of the time to activate/deactivate diss populations", + default=1.0, +) +parser.add_argument( + "--stim_pops", + type=int, + help="number of noise (Poisson) stimulation populations", + default=1, +) +parser.add_argument( + "--internal_delay_min", + type=float, + help="min delay for internal inhibition synapses", + default=2.0, +) +parser.add_argument( + "--internal_max", + type=float, + help="max value for internal inhibition weights", + default=-2.4, +) +parser.add_argument( + "--diss_full", + type=bool, + help="whether the dissipation should last for the full simulation", + default=False, +) +parser.add_argument( + "--tau_syn_E", type=float, help="excitatory synapse time constant", default=5.0 +) +parser.add_argument( + "--stim_full", + type=bool, + help="whether the stimulation should last for the full simulation", + default=False, +) +parser.add_argument( + "--diss_max", type=float, help="max value for dissipation weights", default=-1.5 +) +parser.add_argument("--cm", type=float, help="membrane capacitance", default=0.25) +parser.add_argument( + "--internal_delay_max", + type=float, + help="max delay for internal inhibition synapses", + default=2.0, +) +parser.add_argument( + "--stim_phase", + type=int, + help="a delay for activation of stimulation after the simulation begins", + default=0, +) +parser.add_argument( + "--diss_pops", + type=int, + help="number of noise (Poisson) dissipation populations", + default=1, +) +parser.add_argument( + "--diss_ratio", + type=float, + help="fraction of the dissipation interval to keep dissipation active", + default=1.0, +) +parser.add_argument( + "--stim_delay_min", + type=float, + help="min value of the synaptic delay from the stimulating poisson sources to the principal neurons", + default=1.0, +) +parser.add_argument( + "--stim_shrink", + type=float, + help="shrinkage of the time to activate/deactivate stim populations", + default=1.0, +) +parser.add_argument( + "--v_rest", type=float, help="rest membrane potential", default=-65.0 +) +parser.add_argument( + "--activate_stim", + type=bool, + help="whether stimulation with Poisson sources should be activated", + default=True, +) +parser.add_argument( + "--stim_delay_max", + type=float, + help="max value of the synaptic delay from the stimulating poisson sources to the principal neurons", + default=1.0, +) +parser.add_argument( + "--stim_max", type=float, help="max value for stimulation weights", default=1.5 +) +parser.add_argument( + "--internal_min", + type=float, + help="min value for internal inhibition weights", + default=-2.1, +) +parser.add_argument( + "--stim_ratio", + type=float, + help="fraction of the stimulation interval to keep stimulation active", + default=1.0, +) +parser.add_argument( + "--diss_phase", + type=int, + help="a delay for activation of dissipation after the simulation begins", + default=0, +) +parser.add_argument( + "--cons_max", + type=float, + help="max value for constraints inhibition weights", + default=-1.0, +) +parser.add_argument( + "--activate_diss", + type=bool, + help="whether stimulation with Poisson sources should be activated", + default=True, +) +parser.add_argument( + "--stim_min", type=float, help="min value for stimulation weights", default=0.1 +) +parser.add_argument( + "--cons_min", + type=float, + help="min value for constraints inhibition weights", + default=-0.5, +) +parser.add_argument( + "--diss_delay_min", + type=float, + help="min value of the synaptic delay from the dissipating poisson sources to the principal neurons", + default=1.0, +) +parser.add_argument( + "--v_reset", type=float, help="reset membrane potential", default=-70.0 +) +parser.add_argument( + "--plot_dynamics", type=bool, help="whether plot dynamics or not", default=False +) +parser.add_argument( + "--tau_syn_I", type=float, help="inhibitory synapse time constant", default=5.0 +) +parser.add_argument( + "--clues_inh", type=bool, help="whether allow inhibition over clues", default=True +) +parser.add_argument( + "--diss_min", type=float, help="min value for dissipation weights", default=-1.5 +) +parser.add_argument( + "--diss_delay_max", + type=float, + help="max value of the synaptic delay from the dissipating poisson sources to the principal neurons", + default=1.0, +) +parser.add_argument("--tau_m", type=float, help="membrane time constant", default=20.0) +parser.add_argument( + "--run_time", type=int, help="duration of the simulation", default=500000 +) +parser.add_argument( + "--v_thresh", type=float, help="firing threshold voltage", default=-50.0 +) +parser.add_argument( + "--core_size", + type=int, + help="number of neurons per domain in a variable", + default=42, +) args = parser.parse_args() name = args.name @@ -81,26 +225,45 @@ # SpiNNaker setup. p.setup(timestep=1.0) # Build spiking neural network. -map = CSP(variables_number = 193, domain_size = 4, constraints=world_borders, core_size=args.core_size,run_time=args.run_time) -map.cell_params_lif['i_offset']= args.i_offset -map.cell_params_lif['tau_syn_I']=args.tau_syn_I +map = CSP( + variables_number=193, + domain_size=4, + constraints=world_borders, + core_size=args.core_size, + run_time=args.run_time, +) +map.cell_params_lif["i_offset"] = args.i_offset +map.cell_params_lif["tau_syn_I"] = args.tau_syn_I map.build_domains_pops() -map.internal_inhibition(w_range=[-abs(args.internal_min), -abs(args.internal_max)]) # , w_range= [-0.5,-1.0], d_range=[2.0,2.0]) -map.apply_constraints(w_range=[-abs(args.cons_min), -abs(args.cons_max)], d_range=[2.0, 2.0]) # w_range=[-0.2/2.5,0.0] -map.build_stimulation_pops(n_populations=args.stim_pops, full=args.stim_full,shrink=args.stim_shrink, stim_ratio=args.stim_ratio, - phase=args.stim_phase) # , n_populations=5,shrink=0.750, stim_ratio=0.50, full=False) #, phase=15000) -map.stimulate_cores(w_range=[args.stim_max,args.stim_max], d_range=[1.0,1.0]) -map.build_dissipation_pops(d_populations=args.diss_pops,shrink=args.diss_shrink, phase=args.diss_phase) # , d_populations=2, shrink=0.250, stim_ratio=0.2, full=False, phase=args.phase) +map.internal_inhibition( + w_range=[-abs(args.internal_min), -abs(args.internal_max)] +) # , w_range= [-0.5,-1.0], d_range=[2.0,2.0]) +map.apply_constraints( + w_range=[-abs(args.cons_min), -abs(args.cons_max)], d_range=[2.0, 2.0] +) # w_range=[-0.2/2.5,0.0] +map.build_stimulation_pops( + n_populations=args.stim_pops, + full=args.stim_full, + shrink=args.stim_shrink, + stim_ratio=args.stim_ratio, + phase=args.stim_phase, +) # , n_populations=5,shrink=0.750, stim_ratio=0.50, full=False) #, phase=15000) +map.stimulate_cores(w_range=[args.stim_max, args.stim_max], d_range=[1.0, 1.0]) +map.build_dissipation_pops( + d_populations=args.diss_pops, shrink=args.diss_shrink, phase=args.diss_phase +) # , d_populations=2, shrink=0.250, stim_ratio=0.2, full=False, phase=args.phase) if args.activate_diss: - map.depress_cores(w_range=[-abs(args.diss_min), -abs(args.diss_max)], d_range=[1.0,1.0]) -map.initialize() #, v_range=[-65.,-55.]) + map.depress_cores( + w_range=[-abs(args.diss_min), -abs(args.diss_max)], d_range=[1.0, 1.0] + ) +map.initialize() # , v_range=[-65.,-55.]) # Record spikes from variable populations. map.recording() # Run simulation. p.run(args.run_time) # Save recorded spikes. map.save(name) -map.report_network_params('report_%s'%name) +map.report_network_params("report_%s" % name) # End simulation. p.end() # Plot entropy. diff --git a/examples/run.sh b/examples/run.sh old mode 100644 new mode 100755 index 55eb633..4abb4ed --- a/examples/run.sh +++ b/examples/run.sh @@ -40,6 +40,6 @@ do echo results/${NAME}_trial_${i}_spikes_binary is already present. else echo running trial_${i} for ${NAME} - python2 $EXE $NAME --name ${NAME}_trial_${i} $PARAM $VALUE + python3 $EXE $NAME --name ${NAME}_trial_${i} $PARAM $VALUE fi done \ No newline at end of file diff --git a/examples/spin_lattice.py b/examples/spin_lattice.py index 7723852..d6a4d07 100644 --- a/examples/spin_lattice.py +++ b/examples/spin_lattice.py @@ -3,40 +3,70 @@ # # "Using Stochastic Spiking Neural Networks on SpiNNaker to Solve Constraint Satisfaction Problems" # Submitted to the journal Frontiers in Neuroscience| Neuromorphic Engineering -#----------------------------------------------------------------------------------------------------------------------- +# ----------------------------------------------------------------------------------------------------------------------- """ Spiking Neural Network Solver for magnetic configurations of Ising spin lattices. -This system represents an ensemble of spins each one with up and down as possible states, the constraints represent +This system represents an ensemble of spins each one with up and down as possible states, the constraints represent nearest-neighbours ferromagnetic, antiferromagnetic or mixed coupling. The solver works with 1-dimensional, 2-dimensional and 3-dimensional spin systems. """ -import spynnaker7.pyNN as p # simulator +import spynnaker8 as p # simulator import argparse + try: from spinnaker_csp import CSP, plot_entropy, spin2csp except: import sys import os - sys.path.append(os.getcwd()+'/..') + + sys.path.append(os.getcwd() + "/..") from spinnaker_csp import CSP, plot_entropy, spin2csp -parser = argparse.ArgumentParser(description='''This script creates a spiking neural network representation of a - regular lattice of Ising spins whose dynamics implements a stochastic - search for satisfiability. Only nearest-neighbours interaction''') -parser.add_argument("lattice", help='name of lattice from: AF3D, FM3D, SG50, SG10, ring', - type=str, default='ring') -parser.add_argument("-n", "--name", help='name to use for naming files', type=str, default=None) -parser.add_argument("-l", "--length", help='number of spins on each side of the lattice', type=int, default=10) +parser = argparse.ArgumentParser( + description="""This script creates a spiking neural network representation of a + regular lattice of Ising spins whose dynamics implements a stochastic + search for satisfiability. Only nearest-neighbours interaction""" +) +parser.add_argument( + "lattice", + help="name of lattice from: AF3D, FM3D, SG50, SG10, ring", + type=str, + default="ring", +) +parser.add_argument( + "-n", "--name", help="name to use for naming files", type=str, default=None +) +parser.add_argument( + "-l", + "--length", + help="number of spins on each side of the lattice", + type=int, + default=10, +) args = parser.parse_args() # Parameters for each configuration. Format: [dimensions, random coupling type?, synapse_type, probability, label]. -systems={'AF3D': [3, False, "inhibitory", 1, "AF3D"], # Antiferromagnetic 3D lattice. - 'FM3D': [3, False, "excitatory", 0, "FM3D"], # Ferromagnetic 3D lattice. - 'SG50': [2, True, "inhibitory", 0.5, "SGI"], # 2D spin glass, 50% probability of antiferromagnetic coupling. - 'SG10': [2, True, "inhibitory", 0.1, "SGII"], # 2D spin glass, 10% probability of antiferromagnetic coupling. - 'ring': [1, False, "inhibitory", 1, "chain"]} # 1D spin system with connected ends. +systems = { + "AF3D": [3, False, "inhibitory", 1, "AF3D"], # Antiferromagnetic 3D lattice. + "FM3D": [3, False, "excitatory", 0, "FM3D"], # Ferromagnetic 3D lattice. + "SG50": [ + 2, + True, + "inhibitory", + 0.5, + "SGI", + ], # 2D spin glass, 50% probability of antiferromagnetic coupling. + "SG10": [ + 2, + True, + "inhibitory", + 0.1, + "SGII", + ], # 2D spin glass, 10% probability of antiferromagnetic coupling. + "ring": [1, False, "inhibitory", 1, "chain"], +} # 1D spin system with connected ends. experiment = systems[args.lattice] dimensions, rn, nature, pAF, name = experiment @@ -51,15 +81,22 @@ run_time = 50000 p.setup(timestep=1.0) # Build spiking neural network. -spin = CSP(variables_number=crystal.var_num, domain_size=crystal.dom_num, constraints=crystal.constraints, - core_size=25,run_time=run_time) -spin.cell_params_lif['i_offset']=0.0 # args.i_offset +spin = CSP( + variables_number=crystal.var_num, + domain_size=crystal.dom_num, + constraints=crystal.constraints, + core_size=25, + run_time=run_time, +) +spin.cell_params_lif["i_offset"] = 0.0 # args.i_offset spin.build_domains_pops() -spin.build_stimulation_pops(n_populations=1, shrink=1.0, stim_ratio=1.0, full=False, phase=0.0) #shrink = 1.0 -spin.build_dissipation_pops(phase= 3000) +spin.build_stimulation_pops( + n_populations=1, shrink=1.0, stim_ratio=1.0, full=False, phase=0.0 +) # shrink = 1.0 +spin.build_dissipation_pops(phase=3000) spin.internal_inhibition() -spin.stimulate_cores() # w_range=[0.001,0.005]) # specification of weights only for FM -spin.depress_cores(w_range=[-4.5,-14.5]) # w_range=[-4.5,-14.5] +spin.stimulate_cores() # w_range=[0.001,0.005]) # specification of weights only for FM +spin.depress_cores(w_range=[-4.5, -14.5]) # w_range=[-4.5,-14.5] spin.apply_constraints(nature, random_cons=rn, pAF=pAF) # Record spikes from variable populations. spin.recording() @@ -67,22 +104,23 @@ p.run(run_time) # Save recorded spikes. spin.save(name) -spin.report_network_params('report_%s'%name) +spin.report_network_params("report_%s" % name) # End simulation. p.end() # Plot entropy. -sol = plot_entropy(name, 200, show=False, cons_type= nature) +sol = plot_entropy(name, 200, show=False, cons_type=nature) # Show solution on std output. if sol: for i in range(len(sol)): - if sol[i] == 0: sol[i]=-1 - print('='*70) - if dimensions==1: - print sol + if sol[i] == 0: + sol[i] = -1 + print("=" * 70) + if dimensions == 1: + print(sol) if dimensions == 2: n = 0 - print'[' + print("[") for i in range(length): - print sol[n:n + length], ',' - n+=length - print']' + print(sol[n : n + length], ",") + n += length + print("]") diff --git a/examples/sudoku.py b/examples/sudoku.py index 204fc2c..365c79e 100644 --- a/examples/sudoku.py +++ b/examples/sudoku.py @@ -3,13 +3,13 @@ # # "Using Stochastic Spiking Neural Networks on SpiNNaker to Solve Constraint Satisfaction Problems" # Submitted to the journal Frontiers in Neuroscience| Neuromorphic Engineering -#----------------------------------------------------------------------------------------------------------------------- +# ----------------------------------------------------------------------------------------------------------------------- """ Spiking Neural Network Solver for Sudoku puzzles. Some example puzzles are available to be imported from the puzzles folder. """ -import spynnaker7.pyNN as p # simulator +import spynnaker8 as p # simulator import argparse try: @@ -17,40 +17,53 @@ except: import sys import os - sys.path.append(os.getcwd()+'/..') + + sys.path.append(os.getcwd() + "/..") from spinnaker_csp import CSP, plot_entropy, sudoku2csp, puzzles -#Take puzzle and naming as arguments. -parser = argparse.ArgumentParser(description='''This script creates a spiking neural network representation of a +# Take puzzle and naming as arguments. +parser = argparse.ArgumentParser( + description="""This script creates a spiking neural network representation of a Sudoku puzzle whose dynamics implements a stochastic search for - satisfiability.''') -parser.add_argument("puzzle", help='name of puzzle in puzzles dictionary: easy, hard, AI_escargot or platinum_blonde', - type=str, default='easy') -parser.add_argument("-n", "--name", help='name to use for naming files', type=str, default='sudoku') + satisfiability.""" +) +parser.add_argument( + "puzzle", + help="name of puzzle in puzzles dictionary: easy, hard, AI_escargot or platinum_blonde", + type=str, + default="easy", +) +parser.add_argument( + "-n", "--name", help="name to use for naming files", type=str, default="sudoku" +) args = parser.parse_args() -name=args.name -grid=puzzles[args.puzzle][1] +name = args.name +grid = puzzles[args.puzzle][1] # Show puzzle in std output. -print 'SpiNNaker will run the stochastic search simulation for:' +print("SpiNNaker will run the stochastic search simulation for:") for i in range(9): - print grid[i] + print(grid[i]) # SpiNNaker setup. -run_time = 60000 # simulation run time -p.setup(timestep=1.0) # SpiNNaker machine setup -#translate puzzle to CSP form +run_time = 60000 # simulation run time +p.setup(timestep=1.0) # SpiNNaker machine setup +# translate puzzle to CSP form sk = sudoku2csp(grid) # Build spiking neural network. -sudoku = CSP(sk.var_num, sk.dom_num, sk.constraints, core_size=25, run_time=run_time) # create csp instance +sudoku = CSP( + sk.var_num, sk.dom_num, sk.constraints, core_size=5, run_time=run_time +) # create csp instance sudoku.clues_inhibition = True sudoku.set_clues(sk.clues) sudoku.build_domains_pops() -sudoku.build_stimulation_pops(1, full=True, stim_ratio=1.) +sudoku.build_stimulation_pops(1, full=True, stim_ratio=1.0) sudoku.build_dissipation_pops() -sudoku.internal_inhibition(w_range=[-0.2/2.5, 0.0]) -sudoku.stimulate_cores(w_range=[1.4, 1.6], d_range=[1.0, 1.0], w_clues=[1.8, 2.0]) # , w_clues=[1.5, 1.9]) -sudoku.apply_constraints(w_range=[-0.2/2.5, 0.0]) +sudoku.internal_inhibition(w_range=[-0.2 / 2.5, 0.0]) +sudoku.stimulate_cores( + w_range=[1.4, 1.6], d_range=[1.0, 1.0], w_clues=[1.8, 2.0] +) # , w_clues=[1.5, 1.9]) +# sudoku.apply_constraints(w_range=[-0.2 / 2.5, 0.0]) sudoku.initialize() # Record spikes from variable populations. sudoku.recording() @@ -58,16 +71,17 @@ p.run(run_time) # Save recorded spikes. sudoku.save(name, False) -sudoku.report_network_params('report_%s'%name) +sudoku.report_network_params("report_%s" % name) # End simulation. p.end() # Plot entropy. sol = plot_entropy(name, 200, show=False) # Show solution on std output. if sol is not None: - for i in range(len(sol)): sol[i]=sol[i]+1 - print '='*70 - n=0 + for i in range(len(sol)): + sol[i] = sol[i] + 1 + print("=" * 70) + n = 0 for i in range(9): - print sol[n:n+9] - n+=9 + print(sol[n : n + 9]) + n += 9 diff --git a/spinnaker_csp/__init__.py b/spinnaker_csp/__init__.py index 7d683c4..f807989 100644 --- a/spinnaker_csp/__init__.py +++ b/spinnaker_csp/__init__.py @@ -1,30 +1,30 @@ """ Spiking Neural Network Solver for Constraint Satisfaction Problems. -This package has been developed by Gabriel Fonseca and Steve Furber at The University of Manchester as part of the +This package has been developed by Gabriel Fonseca and Steve Furber at The University of Manchester as part of the paper: "Using Stochastic Spiking Neural Networks on SpiNNaker to Solve Constraint Satisfaction Problems" Submitted to the journal Frontiers in Neuroscience| Neuromorphic Engineering -A constraint satisfaction problem (CSP) is generally described by a set of variables {X_i} taking values over a certain +A constraint satisfaction problem (CSP) is generally described by a set of variables {X_i} taking values over a certain set of domains {D_i}, over which the set of constraints {C_ij} is imposed. The problem consists in finding assignations for all variables X_i's so that all C_ij's are satisfied. To solve a particular CSP import the CSP class from snn_solver.py and create an instance object. To build the solver network -you may implement methods of the CSP class +you may implement methods of the CSP class Additionally, you will be able to keep track of the network dynamics through the functions available in the analysis.py module -e.g. an entropy vs. time plot which colourizes distinctively the regions satisfying the constraints (in Blue) and the ones violating -them (in Red). If an unsatisfying configuration is visited again it will be plotted in green. +e.g. an entropy vs. time plot which colourizes distinctively the regions satisfying the constraints (in Blue) and the ones violating +them (in Red). If an unsatisfying configuration is visited again it will be plotted in green. Depending on the CSP, you will have a straightforward or a complicated description of the constraints, for the later it is advised to create a script translating the original problem to a CSP description, examples of this procedure have been included for: -Sudoku: sudoku2csp.py +Sudoku: sudoku2csp.py Ising Spin Systems spin2csp.py -Travelling Salesman Problem: tsp2csp.py +Travelling Salesman Problem: tsp2csp.py when the constraints are simple to describe, e.g. for an antiferromagnetic spin chain, we generate the constraints list on the example script. In the examples subdirectory of the project folder (SpiNNakerCSPs) we provide example implementations for: @@ -33,16 +33,25 @@ Sudoku Puzzle: sudoku_easy.py, sudoku_hard.py and escargot.py Spin Systems: spin_system.py -In the case of Sudoku, you will find a sudoku_puzzles.py inside the puzzles folder containing some example puzzles to be +In the case of Sudoku, you will find a sudoku_puzzles.py inside the puzzles folder containing some example puzzles to be imported on examples/sudoku.py """ -from snn_creator import CSP -from analysis import plot_entropy -from translators.sudoku2csp import sudoku2csp -from translators.spin2csp import spin2csp -from translators.world_bordering_countries import world_borders, world_countries -from puzzles.sudoku_puzzles import puzzles - -__all__ = ['snn_creator', 'analysis','CSP','plot_entropy','sudoku2csp','puzzles','spin2csp','world_borders', - 'world_countries'] +from .snn_creator import CSP +from .analysis import plot_entropy +from .translators.sudoku2csp import sudoku2csp +from .translators.spin2csp import spin2csp +from .translators.world_bordering_countries import world_borders, world_countries +from .puzzles.sudoku_puzzles import puzzles + +__all__ = [ + "snn_creator", + "analysis", + "CSP", + "plot_entropy", + "sudoku2csp", + "puzzles", + "spin2csp", + "world_borders", + "world_countries", +] diff --git a/spinnaker_csp/analysis.py b/spinnaker_csp/analysis.py index c8b2515..01bdeb8 100644 --- a/spinnaker_csp/analysis.py +++ b/spinnaker_csp/analysis.py @@ -3,10 +3,10 @@ # # "Using Stochastic Spiking Neural Networks on SpiNNaker to Solve Constraint Satisfaction Problems" # Submitted to the journal Frontiers in Neuroscience| Neuromorphic Engineering -#----------------------------------------------------------------------------------------------------------------------- +# ----------------------------------------------------------------------------------------------------------------------- """Implement a set of functions to analyse the spikes generated by the simulation and generate plots. -This module contains a set of functions, to obtain and plot the temporal dependence of the shanon entropy, average +This module contains a set of functions, to obtain and plot the temporal dependence of the shanon entropy, average firing rate, visited configurations and domain subpopulations activity from the spikes file generated in the simulation of the spiking neural network representation of the constraint satisfaction problem. """ @@ -15,22 +15,43 @@ import os from random import randint from scipy.interpolate import UnivariateSpline + # Check whether matplotlib is available for importing or continue without plotting. missing_matplotlib = False try: import matplotlib.pyplot as plt + plt.ioff() except: - print 'matplotlib is not present, continuing without plots' - missing_matplotlib=True + print("matplotlib is not present, continuing without plots") + missing_matplotlib = True -msg = '%s \n'%('='*70) # A separator for readability of messages on standard output. +msg = "%s \n" % ( + "=" * 70 +) # A separator for readability of messages on standard output. -def plot_entropy(spikes_file=None, resolution=200, save_to=False, show=True, draw_pulses=True, - cumulative=True, cumulative_decay = 0.97, splines=True, pop_activity=True, cons_type= "inhibitory", - preprint = True, font=18, rate_units= 'k spikes', plots=True, xrange = None, var_to_plot=None, - TSP=False, lw = 3): - """Generate plot of Shannon entropy, firing rate, visited states and populations activity all vs. time. + +def plot_entropy( + spikes_file=None, + resolution=200, + save_to=False, + show=True, + draw_pulses=True, + cumulative=True, + cumulative_decay=0.97, + splines=True, + pop_activity=True, + cons_type="inhibitory", + preprint=True, + font=18, + rate_units="k spikes", + plots=True, + xrange=None, + var_to_plot=None, + TSP=False, + lw=3, +): + """Generate plot of Shannon entropy, firing rate, visited states and populations activity all vs. time. the entropy plot is colorized according to satisfiability: Red=False, Blue = True, Green = False and configuration already visited. The plot of visited states is black for new configurations and yellow for configurations already @@ -38,21 +59,21 @@ def plot_entropy(spikes_file=None, resolution=200, save_to=False, show=True, dra domain will be plotted in a different color. This function uses auxiliary functions defined below to: - -Get spikes from recorded files. - -Organize spikes as a 3Darray of the form (dom_id)X(var_id)X(spike_times) i.e. a list of lists. - -Pool spikes in time probability bins. - -Normalize probability bins to the total num of spikes (for all digits) in each time bin. - -Find most likely domain for each variable at each time bin. - -Count visited configurations vs. time bins. - -Check satisfiability of constraints for each time bin. - -Get the network average firing rate at each time bin - -Compute entropy S=Sum_{digits}[-p{digit}ln(p{digit})] for each time bin generating an array of the form - (entropy)vs(DeltaT). - -Plot entropy vs. time coloured with satisfiability. - -Plot firing rate vs. time. - -Plot Omega vs. time coloured with repeating states. - -Generate plots of the activity of the competing domains in a given set of variable. - + -Get spikes from recorded files. + -Organize spikes as a 3Darray of the form (dom_id)X(var_id)X(spike_times) i.e. a list of lists. + -Pool spikes in time probability bins. + -Normalize probability bins to the total num of spikes (for all digits) in each time bin. + -Find most likely domain for each variable at each time bin. + -Count visited configurations vs. time bins. + -Check satisfiability of constraints for each time bin. + -Get the network average firing rate at each time bin + -Compute entropy S=Sum_{digits}[-p{digit}ln(p{digit})] for each time bin generating an array of the form + (entropy)vs(DeltaT). + -Plot entropy vs. time coloured with satisfiability. + -Plot firing rate vs. time. + -Plot Omega vs. time coloured with repeating states. + -Generate plots of the activity of the competing domains in a given set of variable. + args: spikes_file: where the spikes are saved. resolution: the size of the time bins in milliseconds. @@ -63,7 +84,7 @@ def plot_entropy(spikes_file=None, resolution=200, save_to=False, show=True, dra cumulative_decay: decay rate for old cumulative contributions. splines: wheter include a spline fit with the activity plots. pop_activity: whether generate plots of the activity of the competing domains in a given variable. - cons_type: inhibitory or excitatory for the synapse type used to implement constraints. + cons_type: inhibitory or excitatory for the synapse type used to implement constraints. preprint: whether include titles. font: font size for labels. rate_units: 'spikes' or 'k spikes' affect the y axis of the firing rate plot. @@ -71,104 +92,189 @@ def plot_entropy(spikes_file=None, resolution=200, save_to=False, show=True, dra xrange: range of the horizontal axis. var_to_plot: plot the domains activity of these variables.It could be a list of the form [v_min, v_max], None, the number of the variable as an integer or 'All' to generate plots for all variables. - TSP: whether save the TSP_winners_matrix to a file for travelling salesman problems. + TSP: whether save the TSP_winners_matrix to a file for travelling salesman problems. lw:line width to be used in the plots. - - returns: + + returns: plots and lists of Shannon entropy, firing rate and visited states vs time. """ spikes, params = load_data(spikes_file) - run_time, variables_number, size, domain_size, core_size, constraints, stim_times, diss_times = params - time_bins = run_time/resolution - probabilities, spikecount, pT = compute_probabilities(spikes, variables_number, domain_size, core_size, time_bins, - resolution, cumulative, cumulative_decay) - H, max_H, p_max = compute_entropy(probabilities, variables_number, domain_size, time_bins) + ( + run_time, + variables_number, + size, + domain_size, + core_size, + constraints, + stim_times, + diss_times, + ) = params + time_bins = int(run_time / resolution) + probabilities, spikecount, pT = compute_probabilities( + spikes, + variables_number, + domain_size, + core_size, + time_bins, + resolution, + cumulative, + cumulative_decay, + ) + H, max_H, p_max = compute_entropy( + probabilities, variables_number, domain_size, time_bins + ) satisfiability = constraint_checker(p_max, constraints, cons_type) change = state_change_checker(p_max) is_state_new, visited_states = new_state(p_max) - if save_to == False: save_to = spikes_file + if save_to == False: + save_to = spikes_file if not missing_matplotlib: if plots: - entropy_plot(H, time_bins, run_time if xrange == None else xrange, resolution, satisfiability, is_state_new, - change, pT, draw_pulses, stim_times, diss_times, save_to, max_H, show, visited_states, - preprint, font, rate_units) + entropy_plot( + H, + time_bins, + run_time if xrange == None else xrange, + resolution, + satisfiability, + is_state_new, + change, + pT, + draw_pulses, + stim_times, + diss_times, + save_to, + max_H, + show, + visited_states, + preprint, + font, + rate_units, + ) # save entropy vs. time plot and list to local files if TSP: - with open('TSP_winners_matrix', 'w') as file: + with open("TSP_winners_matrix", "w") as file: sj.dump(p_max, file) - with open('results/entropy_%s.txt' % save_to, 'w+') as file: + with open("results/entropy_%s.txt" % save_to, "w+") as file: np.savetxt(file, [H, satisfiability]) # ------------------------- pop activity if not missing_matplotlib: if pop_activity: - plot_pop_activity(spikecount, time_bins, variables_number, domain_size, stim_times, diss_times, draw_pulses, - save_to, splines, font=font, resolution=resolution, run_time=run_time if xrange == None - else xrange, - var_to_plot=var_to_plot, show=show, lw=lw) + plot_pop_activity( + spikecount, + time_bins, + variables_number, + domain_size, + stim_times, + diss_times, + draw_pulses, + save_to, + splines, + font=font, + resolution=resolution, + run_time=run_time if xrange == None else xrange, + var_to_plot=var_to_plot, + show=show, + lw=lw, + ) sol, sol_time = get_solution(p_max, satisfiability) if sol_time is not None: - print msg, """The simulation found a solution for the first time + print( + msg, + """The simulation found a solution for the first time at time %d, at state %d, the total number of states visited is:%d - """ % (sol_time * resolution, visited_states[sol_time + 1], visited_states[-1]) + """ + % (sol_time * resolution, visited_states[sol_time + 1], visited_states[-1]), + ) return sol + def load_data(prefix=None): """Get spikes from file. - + args: - prefix: the prefix of the name of the file containing the spikes to be processed the file name will be of - the form prefix_spikes_binary. + prefix: the prefix of the name of the file containing the spikes to be processed the file name will be of + the form prefix_spikes_binary. """ - with open('results/%s_spikes_binary' % (prefix)) as database: - params = np.load(database) - run_time, variables_number, size, domain_size, core_size, constraints, stim_times, diss_times = params - spikes = [[] for variable in range(variables_number)] - for variable in range(variables_number): - spikes[variable] = np.load(database) + # with open("results/%s_spikes_binary.npy" % (prefix)) as database: + npyfile = np.load("results/%s_spikes_binary.npz" % (prefix), allow_pickle=True) + params = npyfile["params"] + ( + run_time, + variables_number, + size, + domain_size, + core_size, + constraints, + stim_times, + diss_times, + ) = params + spikes = [[] for variable in range(variables_number)] + for variable in range(variables_number): + spikes[variable] = npyfile["arr_%s" % (variable)] return spikes, params -def compute_probabilities(spikes, variables_number, domain_size, core_size, time_bins, resolution, - cumulative, cumulative_decay): + +def compute_probabilities( + spikes, + variables_number, + domain_size, + core_size, + time_bins, + resolution, + cumulative, + cumulative_decay, +): """Process spikes into firing probabilities using time bins. - + args: spikes: the array of firing times from each neuron, it is extracted from a file with load_data(file). variables_number: number of variables of the CSP. domain_size: number of possible values in the domain of each variable. core_size: number of neurons used to represent each domain of each variable. - time_bins: the number of time bins. + time_bins: the number of time bins. resolution: the size of each time bin. cumulative: whether probabilities are made cummulative or not. - cumulative_decay: decay rate for old cumulative contributions to the probability. - + cumulative_decay: decay rate for old cumulative contributions to the probability. + returns: probabilities: probability matrix, probability per domain per time bin per variable. spikecount: spikes per time bin per domain per variable. - pT: array of the sum of the probabilities of all domains, for each variable and for each time. + pT: array of the sum of the probabilities of all domains, for each variable and for each time. """ - probabilities = [[[0.0 for dom in range(domain_size)] for t in range(time_bins)] for var in - range(variables_number)] - spikecount = [[[0.0 for t in range(time_bins)] for dom in range(domain_size)] for var in - range(variables_number)] + probabilities = [ + [[0.0 for dom in range(domain_size)] for t in range(time_bins)] + for var in range(variables_number) + ] + spikecount = [ + [[0.0 for t in range(time_bins)] for dom in range(domain_size)] + for var in range(variables_number) + ] pT = [[0.0 for i in range(time_bins)] for var in range(variables_number)] max_spikes = 0 # Count spikes per domain per time bin per variable., write to a probability matrix. for var in range(variables_number): for spike in spikes[var]: - probabilities[var][int(spike[1] / resolution)][int(spike[0] / core_size)] += 1 + probabilities[var][int(spike[1] / resolution)][ + int(spike[0] / core_size) + ] += 1 spikecount[var][int(spike[0] / core_size)][int(spike[1] / resolution)] += 1 for time in range(time_bins): pT[var][time] = sum(probabilities[var][time]) - if pT[var][time] > max_spikes: max_spikes = pT[var][time] + if pT[var][time] > max_spikes: + max_spikes = pT[var][time] if cumulative: for dom in range(domain_size): for bin in range(time_bins - 1): - probabilities[var][bin + 1][dom] += probabilities[var][bin][dom] * cumulative_decay + probabilities[var][bin + 1][dom] += ( + probabilities[var][bin][dom] * cumulative_decay + ) for bin in range(time_bins): - p_tot = sum(probabilities[var][bin]) # Total number of spikes per time bin per variable. + p_tot = sum( + probabilities[var][bin] + ) # Total number of spikes per time bin per variable. for dom in range(domain_size): if p_tot != 0.0: probabilities[var][bin][dom] = probabilities[var][bin][dom] / p_tot @@ -176,24 +282,27 @@ def compute_probabilities(spikes, variables_number, domain_size, core_size, time probabilities[var][bin][dom] = None return probabilities, spikecount, pT + def compute_entropy(probabilities, variables_number, domain_size, time_bins): """Compute total entropy per time_bin and find the winner digit for each variable per time bin. - + args: - probabilities: the probability matrix generated by compute_probabilities, probability per domain per time bin + probabilities: the probability matrix generated by compute_probabilities, probability per domain per time bin per variable. variables_number: number of variables of the CSP. domain_size: number of possible values in the domain of each variable. - time_bins: the number of time bins. - + time_bins: the number of time bins. + returns: H: entropy list a value per each time bin. - max_H: theoretical maximum entropy. + max_H: theoretical maximum entropy. p_max: probability of the winner domain per time bin for each variable (array). """ H = [0.0 for i in range(time_bins)] bits = 1 / np.log(2.0) # for conversion to log2 - max_H = variables_number * np.log(domain_size) * bits # maximum possible entropy - all nos equally likely + max_H = ( + variables_number * np.log(domain_size) * bits + ) # maximum possible entropy - all nos equally likely p_max = [[[0, 0.0] for bin in range(time_bins)] for var in range(variables_number)] for t in range(time_bins): for var in range(variables_number): @@ -202,20 +311,25 @@ def compute_entropy(probabilities, variables_number, domain_size, time_bins): if probabilities[var][t][dom] > p_max[var][t][1]: p_max[var][t] = [dom, probabilities[var][t][dom]] if probabilities[var][t][dom] > 0.0: - H[t] += -probabilities[var][t][dom] * np.log(probabilities[var][t][dom]) * bits + H[t] += ( + -probabilities[var][t][dom] + * np.log(probabilities[var][t][dom]) + * bits + ) return H, max_H, p_max + def constraint_checker(winners_matrix, constraints, cons_type): """Check satisfiability of all constraints at each time_bin. args: - winners_matrix: a matrix of the values taken by each variable at each time_bin. Each value in the matrix is of - the form [value, probability] where the probability is measured with regard to the other possible values. + winners_matrix: a matrix of the values taken by each variable at each time_bin. Each value in the matrix is of + the form [value, probability] where the probability is measured with regard to the other possible values. Satisfiability will be checked for each time_bin. - constraints: list of constraints defining the CSP. - + constraints: list of constraints defining the CSP. + returns: - satisfiability: array of truth values of satisfiability for each time bin. + satisfiability: array of truth values of satisfiability for each time bin. """ satisfiability = [] time_bins = np.shape(winners_matrix)[1] @@ -223,40 +337,45 @@ def constraint_checker(winners_matrix, constraints, cons_type): # violations = 0 truth_value = None for constraint in constraints: - source = constraint['source'] - target = constraint['target'] - if cons_type is "inhibitory": - if winners_matrix[source][time][0] != winners_matrix[target][time][ - 0]: # winners_matrix[variable][time][[val, prob]] + source = constraint["source"] + target = constraint["target"] + if cons_type == "inhibitory": + if ( + winners_matrix[source][time][0] != winners_matrix[target][time][0] + ): # winners_matrix[variable][time][[val, prob]] truth_value = True else: truth_value = False # Break the code at first violated constraint, # for now we disregard the number of constraint violations. break # violations += 1 - if cons_type is "excitatory": - if winners_matrix[source][time][0] == winners_matrix[target][time][ - 0]: # winners_matrix[variable][time][[val, prob]] + if cons_type == "excitatory": + if ( + winners_matrix[source][time][0] == winners_matrix[target][time][0] + ): # winners_matrix[variable][time][[val, prob]] truth_value = True else: truth_value = False # Break the code at first violated constraint, # for now we disregard the number of constraint violations. break # violations += 1 - satisfiability.append(truth_value) # satisfiability.append([truth_value, violations]) + satisfiability.append( + truth_value + ) # satisfiability.append([truth_value, violations]) return satisfiability + def state_change_checker(winners_matrix): - """ Check if the state of the system changed between two consecutive times t and t'. + """Check if the state of the system changed between two consecutive times t and t'. State here is defined with respect to the firing of spikes and not to the membrane potential value. args: - winners_matrix: a matrix of the values taken by each variable at each time_bin. Each value in the matrix is of - the form [value, probability] where the probability is measured with regard to the other possible values. - + winners_matrix: a matrix of the values taken by each variable at each time_bin. Each value in the matrix is of + the form [value, probability] where the probability is measured with regard to the other possible values. + returns: - state_change: array of truth value for change of state at each time bin. + state_change: array of truth value for change of state at each time bin. """ state_change = [True] time_bins = np.shape(winners_matrix)[1] @@ -264,7 +383,10 @@ def state_change_checker(winners_matrix): for time in range(time_bins - 1): truth_value = None for variable in range(number_variables): - if winners_matrix[variable][time][0] == winners_matrix[variable][time + 1][0]: + if ( + winners_matrix[variable][time][0] + == winners_matrix[variable][time + 1][0] + ): truth_value = False else: truth_value = True @@ -272,14 +394,15 @@ def state_change_checker(winners_matrix): state_change.append(truth_value) return state_change + def get_solution(winners_matrix, satisfiability): """If found, get the solution and the time at which it was found. - + args: - winners_matrix: a matrix of the values taken by each variable at each time_bin. Each value in the matrix is of + winners_matrix: a matrix of the values taken by each variable at each time_bin. Each value in the matrix is of the form [value, probability] where the probability is measured with regard to the other possible values. - satisfiability: array of truth values of satisfiability for each time bin. - + satisfiability: array of truth values of satisfiability for each time bin. + returns: solution: assignation of values for the CSP variables such that all constraints are satisfiesd. solution_time: time at which the network found the solution for the first time. @@ -293,125 +416,177 @@ def get_solution(winners_matrix, satisfiability): break if solution_time == None: last_bin = len(satisfiability) - 1 - print msg, 'the system did not find a solution, the last configuration of the network was:' + print( + msg, + "the system did not find a solution, the last configuration of the network was:", + ) last = [var[last_bin][0] for var in winners_matrix] - print last + print(last) else: - print msg, 'The simulation found a solution for the first time at bin %d' % solution_time + print( + msg, + "The simulation found a solution for the first time at bin %d" + % solution_time, + ) return solution, solution_time def new_state(winners_matrix): """Check if the current state of the neural network has been visited at any previous time. - State here is defined with respect to the firing of spikes and not to the value of the membrane potential. + State here is defined with respect to the firing of spikes and not to the value of the membrane potential. args: - winners_matrix: a matrix of the values taken by each variable at each time_bin. Each value in the matrix is of + winners_matrix: a matrix of the values taken by each variable at each time_bin. Each value in the matrix is of the form [value, probability] where the probability is measured with regard to the other possible values. - + returns: - newness: array of truth values one per time bin, True if the state is new (never visited before) and False + newness: array of truth values one per time bin, True if the state is new (never visited before) and False otherwise. visited_states: array with the number of configurations that have been visited at each time. """ newness = [] time_bins = np.shape(winners_matrix)[1] variables_number = np.shape(winners_matrix)[0] - visited_configurations = [] # Will save the configuration of the network at each time. - visited_states = [0] # Will save the number of configurations that have been visited at each time. + visited_configurations = ( + [] + ) # Will save the configuration of the network at each time. + visited_states = [ + 0 + ] # Will save the number of configurations that have been visited at each time. for time in range(time_bins): is_state_new = None - configuration = [winners_matrix[variable][time][0] for variable in range(variables_number)] + configuration = [ + winners_matrix[variable][time][0] for variable in range(variables_number) + ] if configuration in visited_configurations: is_state_new = False visited_states.append(visited_states[-1]) else: is_state_new = True - visited_states.append(visited_states[-1]+1) + visited_states.append(visited_states[-1] + 1) visited_configurations.append(configuration) newness.append(is_state_new) return newness, visited_states -def entropy_plot(H, time_bins, run_time, resolution, satisfiability, newness, state_change, - pT, draw_pulses, stim_times, diss_times, save_to, max_H, show, visited_states, preprint, font, - rate_units): + +def entropy_plot( + H, + time_bins, + run_time, + resolution, + satisfiability, + newness, + state_change, + pT, + draw_pulses, + stim_times, + diss_times, + save_to, + max_H, + show, + visited_states, + preprint, + font, + rate_units, +): """Plot entropy vs. time. - + args: H: entropy list a value per each time bin. - time_bins: the number of time bins. + time_bins: the number of time bins. run_time: duration of the simulation. resolution: the size of the time bins in milliseconds. - satisfiability: array of truth values of satisfiability for each time bin. - newness: array of truth values one per time bin, True if the state is new (never visited before) and False + satisfiability: array of truth values of satisfiability for each time bin. + newness: array of truth values one per time bin, True if the state is new (never visited before) and False otherwise. - state_change: array of truth value for change of state at each time bin. + state_change: array of truth value for change of state at each time bin. pT: array of the sum of the probabilities of all domains, for each variable and for each time. draw_pulses: whether draw Blue (Red) vertical lines at stimulating (depressing) noise activation times. stim_times: activation times of the stimulating noise pulses. diss_times: activation times of the depressing noise pulses. save_to: prefix of the file name for the entropy plot, the full name will be prefix_entropy.png. - max_H: theoretical maximum entropy. + max_H: theoretical maximum entropy. show: whether open the figures generated visited_states: array with the number of configurations that have been visited at each time. preprint: whether include titles. font: font size for labels. - rate_units: 'spikes' or 'k spikes' affect the y axis of the firing rate plot. + rate_units: 'spikes' or 'k spikes' affect the y axis of the firing rate plot. """ - plt.figure(1, figsize=[8,10]) - plt.tick_params(axis='both', which='major', labelsize=30) + plt.figure(1, figsize=[8, 10]) + plt.tick_params(axis="both", which="major", labelsize=30) plt.subplot(311) # numrows, numcols, fignum. for time in range(time_bins): - state_change_color = 'r.' if state_change[time] else 'g.' - plt.plot(time * resolution, H[time], 'b.' if satisfiability[time] else state_change_color) - plt.plot(time * resolution, -1., 'k.' if newness[time] else 'y.') + state_change_color = "r." if state_change[time] else "g." + plt.plot( + time * resolution, + H[time], + "b." if satisfiability[time] else state_change_color, + ) + plt.plot(time * resolution, -1.0, "k." if newness[time] else "y.") if draw_pulses: for i in stim_times: - plt.axvline(x=i, color='b', linestyle='dashed') + plt.axvline(x=i, color="b", linestyle="dashed") for i in diss_times: - plt.axvline(x=i, color='r', linestyle='dashed') - if preprint: plt.title(save_to) - plt.ylabel(r'$H$(bits)', fontsize=font) + plt.axvline(x=i, color="r", linestyle="dashed") + if preprint: + plt.title(save_to) + plt.ylabel(r"$H$(bits)", fontsize=font) # plt.xlabel('time (ms)',fontsize=font) - plt.axis([-2, run_time, -10, max(H)+20]) - plt.tick_params(axis='both', which='major', labelsize=15) + plt.axis([-2, run_time, -10, max(H) + 20]) + plt.tick_params(axis="both", which="major", labelsize=15) plt.subplot(312) - conversor = {'k spikes':1000, 'spikes':1} + conversor = {"k spikes": 1000, "spikes": 1} nu = np.divide(np.array(pT).sum(0), conversor[rate_units]) times = [i * resolution for i in range(time_bins)] - plt.plot(times, nu, 'k-') - plt.ylabel(r'$\nu$ (%s/s)'%(rate_units), fontsize=font) + plt.plot(times, nu, "k-") + plt.ylabel(r"$\nu$ (%s/s)" % (rate_units), fontsize=font) # plt.axis([-2, run_time, -10, max(H)+20]) - plt.tick_params(axis='both', which='major', labelsize=15) + plt.tick_params(axis="both", which="major", labelsize=15) # plt.xlabel('time (ms)', fontsize=font) plt.subplot(313) for time in range(time_bins): - plt.plot(time * resolution, visited_states[time], 'k.' if newness[time] else 'y.') - plt.ylabel(r'$\Omega$',fontsize=font) - plt.xlabel('time (ms)', fontsize=font) + plt.plot( + time * resolution, visited_states[time], "k." if newness[time] else "y." + ) + plt.ylabel(r"$\Omega$", fontsize=font) + plt.xlabel("time (ms)", fontsize=font) plt.axis([-2, run_time, -max(visited_states) * 0.2, max(visited_states) * 1.3]) - plt.tick_params(axis='both', which='major', labelsize=15) - plt.savefig('results/%s_entropy.png' % save_to) + plt.tick_params(axis="both", which="major", labelsize=15) + plt.savefig("results/%s_entropy.png" % save_to) if show: plt.show() plt.close() return -def plot_pop_activity(spikecount, time_bins, variables_number, domain_size, stim_times, diss_times, draw_pulses, - save_to, splines=True, font=20, noticks = False, resolution=None, run_time = None, - var_to_plot=None, - show=True, lw=1.0): - """Generate plots of the activity of the competing domains in a given set of variables. - +def plot_pop_activity( + spikecount, + time_bins, + variables_number, + domain_size, + stim_times, + diss_times, + draw_pulses, + save_to, + splines=True, + font=20, + noticks=False, + resolution=None, + run_time=None, + var_to_plot=None, + show=True, + lw=1.0, +): + """Generate plots of the activity of the competing domains in a given set of variables. + The plot for each domain will be plotted in a different color. - + args: spikecount: spikes per time bin per domain per variable. - time_bins: the number of time bins. + time_bins: the number of time bins. variables_number: number of variables of the CSP. domain_size: number of possible values in the domain of each variable. stim_times: activation times of the stimulating noise pulses. @@ -424,27 +599,30 @@ def plot_pop_activity(spikecount, time_bins, variables_number, domain_size, stim resolution: the size of the time bins in milliseconds. run_time: duration of the simulation. var_to_plot: plot the domains activity of these variables.It could be a list of the form [v_min, v_max], - None, the number of the variable as an integer or 'All' to generate plots for all variables. + None, the number of the variable as an integer or 'All' to generate plots for all variables. show: whether open the figures generated. lw:line width to be used in the plots. """ - if not os.path.exists('results/Dynamics'): - os.makedirs('results/Dynamics') - pop_activity = [[[0.0 for time in range(time_bins)] for domain in range(domain_size)] for variable in - range(variables_number)] + if not os.path.exists("results/Dynamics"): + os.makedirs("results/Dynamics") + pop_activity = [ + [[0.0 for time in range(time_bins)] for domain in range(domain_size)] + for variable in range(variables_number) + ] for variable in range(variables_number): for domain in range(domain_size): for time in range(time_bins): if spikecount[variable][domain][time] != 0.0: - pop_activity[variable][domain][time] = \ - spikecount[variable][domain][time]/max(spikecount[variable][domain]) + pop_activity[variable][domain][time] = spikecount[variable][domain][ + time + ] / max(spikecount[variable][domain]) else: pop_activity[variable][domain][time] = 0.0 if isinstance(var_to_plot, int): - var_to_plot=[var_to_plot] + var_to_plot = [var_to_plot] elif var_to_plot == None: var_to_plot = [randint(0, variables_number - 1)] - elif var_to_plot == 'All': + elif var_to_plot == "All": var_to_plot = range(variables_number) counter = 0 for variable in var_to_plot: @@ -452,26 +630,37 @@ def plot_pop_activity(spikecount, time_bins, variables_number, domain_size, stim if noticks: plt.xticks([]) plt.yticks([]) - colors = ['r', 'b', 'g', 'c', 'm', 'y', 'k', '0.75', 'burlywood'] - times = np.multiply(list(np.arange(0, time_bins)), resolution) # verify if it is necesary to be a list + colors = ["r", "b", "g", "c", "m", "y", "k", "0.75", "burlywood"] + times = np.multiply( + list(np.arange(0, time_bins)), resolution + ) # verify if it is necesary to be a list for domain in range(domain_size): - plt.plot(times, pop_activity[variable][domain], '.', color=colors[domain], markersize=lw*2) + plt.plot( + times, + pop_activity[variable][domain], + ".", + color=colors[domain], + markersize=lw * 2, + ) if splines: spl = UnivariateSpline(times, pop_activity[variable][domain]) - spl.set_smoothing_factor(.10) #3 + spl.set_smoothing_factor(0.10) # 3 xs = np.linspace(0, run_time, 10000) plt.plot(xs, spl(xs), color=colors[domain], linewidth=lw) plt.axis([-2, run_time, 0, 1]) if draw_pulses: for time in stim_times: - plt.axvline(x=time, color='b', linestyle='dashed') + plt.axvline(x=time, color="b", linestyle="dashed") for time in diss_times: - plt.axvline(x=time, color='r', linestyle='dashed') - plt.ylabel('$\hat{A}$',fontsize=font) - plt.xlabel('time (ms)', fontsize=font) - plt.tick_params(axis='both', which='major', labelsize=15) - plt.savefig("results/Dynamics/%s_dynamics_variable_%d.png" % (save_to, var_to_plot[counter])) # save plot + plt.axvline(x=time, color="r", linestyle="dashed") + plt.ylabel("$\hat{A}$", fontsize=font) + plt.xlabel("time (ms)", fontsize=font) + plt.tick_params(axis="both", which="major", labelsize=15) + plt.savefig( + "results/Dynamics/%s_dynamics_variable_%d.png" + % (save_to, var_to_plot[counter]) + ) # save plot if show: plt.show() plt.close() - counter += 1 \ No newline at end of file + counter += 1 diff --git a/spinnaker_csp/puzzles/sudoku_puzzles.py b/spinnaker_csp/puzzles/sudoku_puzzles.py index 040c7f5..d70992c 100644 --- a/spinnaker_csp/puzzles/sudoku_puzzles.py +++ b/spinnaker_csp/puzzles/sudoku_puzzles.py @@ -3,103 +3,129 @@ the puzzles are containned on the dictionary puzzles, keys are the name of the puzzle and values are tuples with the puzzle as first element and solution as second element. """ -puzzles={ -#--------------------------------------------------------------------- -'Dream': ("dream", -#--------------------------------------------------------------------- -[[0 for x in range(9)] for y in range(9)], -None), -#--------------------------------------------------------------------- -'easy':("easy", # easy from doi:10.1038/srep00725 -#--------------------------------------- -[[0, 4, 0, 8, 0, 5, 2, 0, 0], -[0, 2, 0, 0, 4, 0, 0, 5, 0], -[5, 0, 0, 0, 0, 0, 0, 0, 4], -[0, 9, 0, 0, 0, 3, 1, 2, 0], -[1, 0, 6, 0, 7, 8, 0, 0, 3], -[3, 7, 0, 9, 0, 4, 0, 8, 0], -[0, 0, 0, 0, 0, 6, 7, 0, 0], -[0, 0, 8, 3, 5, 9, 0, 1, 0], -[0, 1, 9, 0, 0, 7, 6, 0, 0]], -#--------------------------------------- -[[9, 4, 7, 8, 3, 5, 2, 6, 1], -[6, 2, 3, 7, 4, 1, 8, 5, 9], -[5, 8, 1, 6, 9, 2, 3, 7, 4], -[8, 9, 4, 5, 6, 3, 1, 2, 7], -[1, 5, 6, 2, 7, 8, 9, 4, 3], -[3, 7, 2, 9, 1, 4, 5, 8, 6], -[4, 3, 5, 1, 2, 6, 7, 9, 8], -[7, 6, 8, 3, 5, 9, 4, 1, 2], -[2, 1, 9, 4, 8, 7, 6, 3, 5]]), -#--------------------------------------------------------------------- -'hard':('hard', # hard puzzle from https://doi.org/10.1371/journal.pcbi.1003311 -#--------------------------------------------------------------------- -[[8, 0, 5, 0, 0, 0, 0, 3, 0], -[0, 3, 0, 9, 0, 0, 0, 0, 0], -[4, 0, 6, 0, 3, 0, 0, 0, 0], -[6, 0, 0, 0, 1, 0, 9, 0, 0], -[0, 5, 0, 3, 0, 8, 0, 7, 0], -[0, 0, 9, 0, 4, 0, 0, 0, 1], -[0, 0, 0, 0, 2, 0, 3, 0, 8], -[0, 0, 0, 0, 0, 9, 0, 2, 0], -[0, 7, 0, 0, 0, 0, 5, 0, 4]], -#--------------------------------------------------------------------- -[[8, 1, 5, 6, 7, 4, 2, 3, 9], -[7, 3, 2, 9, 5, 1, 4, 8, 6], -[4, 9, 6, 8, 3, 2, 7, 1, 5], -[6, 8, 7, 2, 1, 5, 9, 4, 3], -[1, 5, 4, 3, 9, 8, 6, 7, 2], -[3, 2, 9, 7, 4, 6, 8, 5, 1], -[9, 4, 1, 5, 2, 7, 3, 6, 8], -[5, 6, 3, 4, 8, 9, 1, 2, 7], -[2, 7, 8, 1, 6, 3, 5, 9, 4]]), -#--------------------------------------------------------------------- -'AI_escargot': ('AI_escargot', -#--------------------------------------------------------------------- -[[1, 0, 0, 0, 0, 7, 0, 9, 0], -[0, 3, 0, 0, 2, 0, 0, 0, 8], -[0, 0, 9, 6, 0, 0, 5, 0, 0], -[0, 0, 5, 3, 0, 0, 9, 0, 0], -[0, 1, 0, 0, 8, 0, 0, 0, 2], -[6, 0, 0, 0, 0, 4, 0, 0, 0], -[3, 0, 0, 0, 0, 0, 0, 1, 0], -[0, 4, 0, 0, 0, 0, 0, 0, 7], -[0, 0, 7, 0, 0, 0, 3, 0, 0]], -#--------------------------------------------------------------------- -[[0, 0, 0, 0, 0, 0, 0, 0, 0], -[0, 0, 0, 0, 0, 0, 0, 0, 0], -[0, 0, 0, 0, 0, 0, 0, 0, 0], -[0, 0, 0, 0, 0, 0, 0, 0, 0], -[0, 0, 0, 0, 0, 0, 0, 0, 0], -[0, 0, 0, 0, 0, 0, 0, 0, 0], -[0, 0, 0, 0, 0, 0, 0, 0, 0], -[0, 0, 0, 0, 0, 0, 0, 0, 0], -[0, 0, 0, 0, 0, 0, 0, 0, 0]]), -#--------------------------------------------------------------------- -'platinum_blonde':('platinum_blonde', # hard from doi:10.1038/srep00725 -#--------------------------------------------------------------------- -[[0, 0, 0, 0, 0, 0, 0, 1, 2], -[0, 0, 0, 0, 0, 0, 0, 0, 3], -[0, 0, 2, 3, 0, 0, 4, 0, 0], -[0, 0, 1, 8, 0, 0, 0, 0, 5], -[0, 6, 0, 0, 7, 0, 8, 0, 0], -[0, 0, 0, 0, 0, 9, 0, 0, 0], -[0, 0, 8, 5, 0, 0, 0, 0, 0], -[9, 0, 0, 0, 4, 0, 5, 0, 0], -[4, 7, 0, 0, 0, 6, 0, 0, 0]], -#--------------------------------------------------------------------- -[[8, 3, 9, 4, 6, 5, 7, 1, 2], -[1, 4, 6, 7, 8, 2, 9, 5, 3], -[7, 5, 2, 3, 9, 1, 4, 8, 6], -[3, 9, 1, 8, 2, 4, 6, 7, 5], -[5, 6, 4, 1, 7, 3, 8, 2, 9], -[2, 8, 7, 6, 5, 9, 3, 4, 1], -[6, 2, 8, 5, 3, 7, 1, 9, 4], -[9, 1, 3, 2, 4, 8, 5, 6, 7], -[4, 7, 5, 9, 1, 6, 2, 3, 8]]) +puzzles = { + # --------------------------------------------------------------------- + "Dream": ( + "dream", + # --------------------------------------------------------------------- + [[0 for x in range(9)] for y in range(9)], + None, + ), + # --------------------------------------------------------------------- + "easy": ( + "easy", # easy from doi:10.1038/srep00725 + # --------------------------------------- + [ + [0, 4, 0, 8, 0, 5, 2, 0, 0], + [0, 2, 0, 0, 4, 0, 0, 5, 0], + [5, 0, 0, 0, 0, 0, 0, 0, 4], + [0, 9, 0, 0, 0, 3, 1, 2, 0], + [1, 0, 6, 0, 7, 8, 0, 0, 3], + [3, 7, 0, 9, 0, 4, 0, 8, 0], + [0, 0, 0, 0, 0, 6, 7, 0, 0], + [0, 0, 8, 3, 5, 9, 0, 1, 0], + [0, 1, 9, 0, 0, 7, 6, 0, 0], + ], + # --------------------------------------- + [ + [9, 4, 7, 8, 3, 5, 2, 6, 1], + [6, 2, 3, 7, 4, 1, 8, 5, 9], + [5, 8, 1, 6, 9, 2, 3, 7, 4], + [8, 9, 4, 5, 6, 3, 1, 2, 7], + [1, 5, 6, 2, 7, 8, 9, 4, 3], + [3, 7, 2, 9, 1, 4, 5, 8, 6], + [4, 3, 5, 1, 2, 6, 7, 9, 8], + [7, 6, 8, 3, 5, 9, 4, 1, 2], + [2, 1, 9, 4, 8, 7, 6, 3, 5], + ], + ), + # --------------------------------------------------------------------- + "hard": ( + "hard", # hard puzzle from https://doi.org/10.1371/journal.pcbi.1003311 + # --------------------------------------------------------------------- + [ + [8, 0, 5, 0, 0, 0, 0, 3, 0], + [0, 3, 0, 9, 0, 0, 0, 0, 0], + [4, 0, 6, 0, 3, 0, 0, 0, 0], + [6, 0, 0, 0, 1, 0, 9, 0, 0], + [0, 5, 0, 3, 0, 8, 0, 7, 0], + [0, 0, 9, 0, 4, 0, 0, 0, 1], + [0, 0, 0, 0, 2, 0, 3, 0, 8], + [0, 0, 0, 0, 0, 9, 0, 2, 0], + [0, 7, 0, 0, 0, 0, 5, 0, 4], + ], + # --------------------------------------------------------------------- + [ + [8, 1, 5, 6, 7, 4, 2, 3, 9], + [7, 3, 2, 9, 5, 1, 4, 8, 6], + [4, 9, 6, 8, 3, 2, 7, 1, 5], + [6, 8, 7, 2, 1, 5, 9, 4, 3], + [1, 5, 4, 3, 9, 8, 6, 7, 2], + [3, 2, 9, 7, 4, 6, 8, 5, 1], + [9, 4, 1, 5, 2, 7, 3, 6, 8], + [5, 6, 3, 4, 8, 9, 1, 2, 7], + [2, 7, 8, 1, 6, 3, 5, 9, 4], + ], + ), + # --------------------------------------------------------------------- + "AI_escargot": ( + "AI_escargot", + # --------------------------------------------------------------------- + [ + [1, 0, 0, 0, 0, 7, 0, 9, 0], + [0, 3, 0, 0, 2, 0, 0, 0, 8], + [0, 0, 9, 6, 0, 0, 5, 0, 0], + [0, 0, 5, 3, 0, 0, 9, 0, 0], + [0, 1, 0, 0, 8, 0, 0, 0, 2], + [6, 0, 0, 0, 0, 4, 0, 0, 0], + [3, 0, 0, 0, 0, 0, 0, 1, 0], + [0, 4, 0, 0, 0, 0, 0, 0, 7], + [0, 0, 7, 0, 0, 0, 3, 0, 0], + ], + # --------------------------------------------------------------------- + [ + [0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0], + ], + ), + # --------------------------------------------------------------------- + "platinum_blonde": ( + "platinum_blonde", # hard from doi:10.1038/srep00725 + # --------------------------------------------------------------------- + [ + [0, 0, 0, 0, 0, 0, 0, 1, 2], + [0, 0, 0, 0, 0, 0, 0, 0, 3], + [0, 0, 2, 3, 0, 0, 4, 0, 0], + [0, 0, 1, 8, 0, 0, 0, 0, 5], + [0, 6, 0, 0, 7, 0, 8, 0, 0], + [0, 0, 0, 0, 0, 9, 0, 0, 0], + [0, 0, 8, 5, 0, 0, 0, 0, 0], + [9, 0, 0, 0, 4, 0, 5, 0, 0], + [4, 7, 0, 0, 0, 6, 0, 0, 0], + ], + # --------------------------------------------------------------------- + [ + [8, 3, 9, 4, 6, 5, 7, 1, 2], + [1, 4, 6, 7, 8, 2, 9, 5, 3], + [7, 5, 2, 3, 9, 1, 4, 8, 6], + [3, 9, 1, 8, 2, 4, 6, 7, 5], + [5, 6, 4, 1, 7, 3, 8, 2, 9], + [2, 8, 7, 6, 5, 9, 3, 4, 1], + [6, 2, 8, 5, 3, 7, 1, 9, 4], + [9, 1, 3, 2, 4, 8, 5, 6, 7], + [4, 7, 5, 9, 1, 6, 2, 3, 8], + ], + ), } -#-----------------TEMPLATE--------------------------------------------- +# -----------------TEMPLATE--------------------------------------------- ##--------------------------------------------------------------------- # [[0, 0, 0, 0, 0, 0, 0, 0, 0], # [0, 0, 0, 0, 0, 0, 0, 0, 0], @@ -111,7 +137,7 @@ # [0, 0, 0, 0, 0, 0, 0, 0, 0], # [0, 0, 0, 0, 0, 0, 0, 0, 0]] -#-----------------TEMPLATE 16X16---------------------------------------- +# -----------------TEMPLATE 16X16---------------------------------------- # #--------------------------------------------------------------------- # [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], @@ -128,4 +154,4 @@ # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], -# [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]] \ No newline at end of file +# [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]] diff --git a/spinnaker_csp/snn_creator.py b/spinnaker_csp/snn_creator.py index 1b30399..95e5404 100644 --- a/spinnaker_csp/snn_creator.py +++ b/spinnaker_csp/snn_creator.py @@ -3,7 +3,7 @@ # # "Using Stochastic Spiking Neural Networks on SpiNNaker to Solve Constraint Satisfaction Problems" # Submitted to the journal Frontiers in Neuroscience| Neuromorphic Engineering -#----------------------------------------------------------------------------------------------------------------------- +# ----------------------------------------------------------------------------------------------------------------------- """Implement a framework to map a constraint satisfaction problem into a spiking neural network. This module contains the CSP class, which stands for Constraint Satisfaction Problem. Its methods allow the creation of @@ -11,15 +11,17 @@ are either inhibitory or excitatory. The neurons are stochastically stimulated by spike sources implementing a Poisson process causing the network dynamics to implement a stochastic search of the satisying configuration. """ -import spynnaker7.pyNN as p # simulator +import spynnaker8 as p # simulator from pyNN.random import RandomDistribution import numpy as np import os -msg = '%s \n'%('='*70) # a separator for readability of messages on standard output +msg = "%s \n" % ("=" * 70) # a separator for readability of messages on standard output + class CSP: - """Map a constraint satisfaction problem into a spiking neural network. """ + """Map a constraint satisfaction problem into a spiking neural network.""" + live = False run_time = 30000 # Lists for counting populations to build report. @@ -39,19 +41,28 @@ class CSP: # Whether set_clues populations should receive inhibition from other sources. clues_inhibition = False # Parameters for the leaky integrate and fire neurons. - cell_params_lif = {'cm': 0.25, # nF membrane capacitance - 'i_offset': 0.3, # nA bias current - 'tau_m': 20.0, # ms membrane time constant - 'tau_refrac': 2.0, # ms refractory period - 'tau_syn_E': 5.0, # ms excitatory synapse time constant - 'tau_syn_I': 5.0, # ms inhibitory synapse time constant - 'v_reset': -70.0, # mV reset membrane potential - 'v_rest': -65.0, # mV rest membrane potential - 'v_thresh': -50.0 # mV firing threshold voltage - } - - def __init__(self, variables_number=0, domain_size=0, constraints=[], exc_constraints=[], - core_size=25, directed=False, run_time=30000): + cell_params_lif = { + "cm": 0.25, # nF membrane capacitance + "i_offset": 0.3, # nA bias current + "tau_m": 20.0, # ms membrane time constant + "tau_refrac": 2.0, # ms refractory period + "tau_syn_E": 5.0, # ms excitatory synapse time constant + "tau_syn_I": 5.0, # ms inhibitory synapse time constant + "v_reset": -70.0, # mV reset membrane potential + "v_rest": -65.0, # mV rest membrane potential + "v_thresh": -50.0, # mV firing threshold voltage + } + + def __init__( + self, + variables_number=0, + domain_size=0, + constraints=[], + exc_constraints=[], + core_size=25, + directed=False, + run_time=30000, + ): """Initialize the constraint satisfaction problem spiking neural network. Args: @@ -90,22 +101,31 @@ def build_domains_pops(self): The population size will be self.size = domain_size*core_size. var_pops[i] is the population for variable i including all domain sub-populations, each of size core_size. """ - print msg, 'creating an array of %d neural populations'%(self.variables_number) + print( + msg, "creating an array of %d neural populations" % (self.variables_number) + ) var_pops = [] for variable in range(self.variables_number): - var_pops.append(p.Population(self.size, - p.IF_curr_exp, self.cell_params_lif, - label="var%d" % (variable + 1))) + var_pops.append( + p.Population( + self.size, + p.IF_curr_exp, + self.cell_params_lif, + label="var%d" % (variable + 1), + ) + ) self.var_pops = var_pops - def poisson_params(self, n_populations, full=False, stim_ratio=1.0, shrink=1.0, phase=0.0): + def poisson_params( + self, n_populations, full=False, stim_ratio=1.0, shrink=1.0, phase=0.0 + ): """Define time intervals for activation of the pyNN Poisson noise sources. - This method defines the temporal dependence of the stimulating noise, it is an internal method called by the - build_stimulation_pops method. Here we use the word noise to refer to spike sources implementing a random + This method defines the temporal dependence of the stimulating noise, it is an internal method called by the + build_stimulation_pops method. Here we use the word noise to refer to spike sources implementing a random Poisson process. In pyNN these objects connect with neurons using synapses as if they were neurons too. Currently each SpikeSourcePoisson object accepts only a start time and a duration time, thus to change the noise - level through time one should create n different populations and activate them at different times. + level through time one should create n different populations and activate them at different times. Here we uniformly distribute the start times of the n_populations from phase to run_time. Each population will be active for a period lapso = shrink * self.run_time / n_populations if full=False otherwise will stay active during all run_time. To avoid synchronization of all the noise sources and improve the stochasticity of the @@ -115,13 +135,13 @@ def poisson_params(self, n_populations, full=False, stim_ratio=1.0, shrink=1.0, |comienza-|-------Full Noise--------|--termina| |--delta--| |--delta--| 0%-------100% 100%------0% - |------Noise=(lapso - stim_ratio)---|--stim_ratio--------| + |------Noise=(lapso - stim_ratio)---|--stim_ratio--------| |------lapso=runtime*shrink/n_popultions--------------...| |--phase---|------noise interval = runtime*shrink----------------------------------------...| |-----------------------------------------------run_time----------------------------------------------------...| - - Other stochastic search strategies to solve the CSP may be implemented modifying this method and the - build_stimulation_pops method below. + + Other stochastic search strategies to solve the CSP may be implemented modifying this method and the + build_stimulation_pops method below. args: @@ -132,25 +152,43 @@ def poisson_params(self, n_populations, full=False, stim_ratio=1.0, shrink=1.0, shrink: shrinks the time interval throughout wich the noise populations will be distributed. It defines fraction of the run time, so it should be between 0.0 and 1.0. phase: a waiting time before the first noise population activates. - returns: + returns: list of starting times lists of random distributions for start and duration of noise stimulation populations. """ lapso = shrink * self.run_time / n_populations delta = lapso / self.run_time - comienza = [RandomDistribution("uniform", [lapso * i+phase, lapso * i + delta+phase]) for i in range( - n_populations)] + comienza = [ + RandomDistribution( + "uniform", [lapso * i + phase, lapso * i + delta + phase] + ) + for i in range(n_populations) + ] if full: - termina = [RandomDistribution("uniform", [self.run_time - delta, self.run_time]) for i - in range(n_populations)] + termina = [ + RandomDistribution("uniform", [self.run_time - delta, self.run_time]) + for i in range(n_populations) + ] else: - termina = [RandomDistribution("uniform", [lapso * stim_ratio, lapso * stim_ratio + delta]) for i - in range(n_populations)] - stim_times = [lapso * i+phase for i in range(n_populations)] + termina = [ + RandomDistribution( + "uniform", [lapso * stim_ratio, lapso * stim_ratio + delta] + ) + for i in range(n_populations) + ] + stim_times = [lapso * i + phase for i in range(n_populations)] return stim_times, comienza, termina - def build_stimulation_pops(self, n_populations=1, shrink=1.0, stim_ratio=1.0, rate=(20.0, 20.0), full=True, - phase=0.0, clue_size=None): + def build_stimulation_pops( + self, + n_populations=1, + shrink=1.0, + stim_ratio=1.0, + rate=(20.0, 20.0), + full=True, + phase=0.0, + clue_size=None, + ): """Generate noise sources for each neuron and creates additional stimulation sources for clues. The noise sources are pyNN population objects of the SpikeSourcePoisson type, which generate spikes at times @@ -175,30 +213,56 @@ def build_stimulation_pops(self, n_populations=1, shrink=1.0, stim_ratio=1.0, ra phase: a waiting time before the first noise population activates. clue_size: optional, number of neurons to use to stimulate clues, default value is core_size. """ - print msg, 'creating %d populations of SpikeSourcePoisson noise sources for each variable'%(n_populations) - stim_times, comienza, termina = self.poisson_params(n_populations, full=full, stim_ratio=stim_ratio, - shrink=shrink, phase=phase) + print( + msg, + "creating %d populations of SpikeSourcePoisson noise sources for each variable" + % (n_populations), + ) + stim_times, comienza, termina = self.poisson_params( + n_populations, full=full, stim_ratio=stim_ratio, shrink=shrink, phase=phase + ) if clue_size == None: clue_size = self.core_size stim_pops = [[] for k in range(n_populations)] clues_stim = [] for stimulus in range(n_populations): for variable in range(self.variables_number): - stim_pops[stimulus].append(p.Population(self.size, p.SpikeSourcePoisson, - {"rate": rate[0], "start": comienza[stimulus], "duration": - termina[stimulus]}, label="stim%d_var%d" % (stimulus + 1, variable - + 1))) + stim_pops[stimulus].append( + p.Population( + self.size, + p.SpikeSourcePoisson, + { + "rate": rate[0], + "start": comienza[stimulus].next(), + "duration": termina[stimulus].next(), + }, + label="stim%d_var%d" % (stimulus + 1, variable + 1), + ) + ) if variable in self.clues[0]: - clues_stim.append(p.Population(clue_size, p.SpikeSourcePoisson, - {"rate": rate[1], "start": 0, "duration": self.run_time}, - label='clues_stim%d' % variable)) + clues_stim.append( + p.Population( + clue_size, + p.SpikeSourcePoisson, + {"rate": rate[1], "start": 0, "duration": self.run_time}, + label="clues_stim%d" % variable, + ) + ) self.stim_pops = stim_pops self.clues_stim = clues_stim self.n_populations = n_populations self.stims = stim_times self.clue_size = clue_size - def build_dissipation_pops(self, d_populations=1, shrink=1.0, stim_ratio=1.0, rate=20.0, full=True, phase=0.0): + def build_dissipation_pops( + self, + d_populations=1, + shrink=1.0, + stim_ratio=1.0, + rate=20.0, + full=True, + phase=0.0, + ): """Generate noise sinks for each neuron: pyNN population objects of the type SpikeSourcePoisson. the Poisson neural populations will be inhibitorilly connected to the variable populations, creating a @@ -216,16 +280,30 @@ def build_dissipation_pops(self, d_populations=1, shrink=1.0, stim_ratio=1.0, ra full: controls if the noise deactivations should all happen after run_time or at the lapso width. phase: a waiting time before the first dissipation population activates. """ - print msg, 'creating %d populations of dissipative noise sources for each variable' % (d_populations) - diss_times, comienza, termina = self.poisson_params(d_populations, full=full, stim_ratio=stim_ratio, - shrink=shrink, phase=phase) + print( + msg, + "creating %d populations of dissipative noise sources for each variable" + % (d_populations), + ) + diss_times, comienza, termina = self.poisson_params( + d_populations, full=full, stim_ratio=stim_ratio, shrink=shrink, phase=phase + ) diss_pops = [[] for k in range(d_populations)] for k in range(d_populations): for variable in range(self.variables_number): - diss_pops[k].append(p.Population(self.size, p.SpikeSourcePoisson, - {"rate": rate, "start": comienza[k], "duration": termina[k]}, - label="diss%d_var%d" % (k + 1, variable + 1))) - #TODO: if self.clues_inibition = False do not create the populations for the clues. + diss_pops[k].append( + p.Population( + self.size, + p.SpikeSourcePoisson, + { + "rate": rate, + "start": comienza[k].next(), + "duration": termina[k].next(), + }, + label="diss%d_var%d" % (k + 1, variable + 1), + ) + ) + # TODO: if self.clues_inibition = False do not create the populations for the clues. self.diss_pops = diss_pops self.d_populations = d_populations self.disss = diss_times @@ -241,16 +319,28 @@ def connect_cores(self, w_range=[0.6, 1.2], d_range=[1.0, 1.2]): w_range: range for the random distribution of synaptic weights in the form [w_min, w_max]. d_range: range for the random distribution of synaptic delays in the form [d_min, d_max]. """ - print msg, 'internally connnecting the neurons of each domain of each variable' - delays = RandomDistribution('uniform', d_range) - weights = RandomDistribution('uniform', w_range) - connections = [(m, n, weights.next() if m // self.core_size == n // self.core_size and m != n else 0.0, - delays.next()) for n in range(self.domain_size * self.core_size) for m in - range(self.domain_size * self.core_size)] + print(msg, "internally connnecting the neurons of each domain of each variable") + delays = RandomDistribution("uniform", d_range) + weights = RandomDistribution("uniform", w_range) + connections = [ + ( + m, + n, + weights.next() + if m // self.core_size == n // self.core_size and m != n + else 0.0, + delays.next(), + ) + for n in range(self.domain_size * self.core_size) + for m in range(self.domain_size * self.core_size) + ] for variable in range(self.variables_number): - synapses = p.Projection(self.var_pops[variable], self.var_pops[variable], p.FromListConnector(connections, - safe=True), - target="excitatory") + synapses = p.Projection( + self.var_pops[variable], + self.var_pops[variable], + p.FromListConnector(connections, safe=True), + receptor_type="excitatory", + ) self.core_conns.append(synapses) def internal_inhibition(self, w_range=[-0.2, 0.0], d_range=[2.0, 2.0]): @@ -263,25 +353,40 @@ def internal_inhibition(self, w_range=[-0.2, 0.0], d_range=[2.0, 2.0]): w_range: range for the random distribution of synaptic weights in the form [w_min, w_max]. d_range: range for the random distribution of synaptic delays in the form [d_min, d_max]. """ - print msg, 'Creating lateral inhibition between domains of each variable' - delays = RandomDistribution('uniform', d_range) - weights = RandomDistribution('uniform',w_range) - connections = [(m, n, 0.0 if m // self.core_size == n // self.core_size else weights.next(), delays.next()) for - n in range(self.size) for m in range(self.size)] + print(msg, "Creating lateral inhibition between domains of each variable") + delays = RandomDistribution("uniform", d_range) + weights = RandomDistribution("uniform", w_range) + connections = [ + ( + m, + n, + 0.0 if m // self.core_size == n // self.core_size else weights.next(), + delays.next(), + ) + for n in range(self.size) + for m in range(self.size) + ] for variable in range(self.variables_number): if self.clues_inhibition: - synapses = p.Projection(self.var_pops[variable], self.var_pops[variable], p.FromListConnector( - connections, safe=True), - target="inhibitory") + synapses = p.Projection( + self.var_pops[variable], + self.var_pops[variable], + p.FromListConnector(connections, safe=True), + receptor_type="inhibitory", + ) self.internal_conns.append(synapses) elif variable not in self.clues: - synapses = p.Projection(self.var_pops[variable], self.var_pops[variable], p.FromListConnector( - connections, safe=True), - target="inhibitory") + synapses = p.Projection( + self.var_pops[variable], + self.var_pops[variable], + p.FromListConnector(connections, safe=True), + receptor_type="inhibitory", + ) self.internal_conns.append(synapses) - - def stimulate_cores(self, w_range=[1.4, 1.4], d_range=[1.0, 1.0], w_clues=[1.4, 1.6]): # w_clues=[0.0, 0.2] + def stimulate_cores( + self, w_range=[1.4, 1.4], d_range=[1.0, 1.0], w_clues=[1.4, 1.6] + ): # w_clues=[0.0, 0.2] """Connect stimulating noise sources to variables populations. args: @@ -290,24 +395,43 @@ def stimulate_cores(self, w_range=[1.4, 1.4], d_range=[1.0, 1.0], w_clues=[1.4, w_clues: clues specific range for the random distribution of synaptic weights in the form [w_min, w_max]. """ p.set_number_of_neurons_per_core(p.IF_curr_exp, 150) - print msg, 'connecting Poisson noise sources to neural populations for stimulation' - delays = RandomDistribution('uniform', d_range) - weights = RandomDistribution('uniform', w_range) + print( + msg, + "connecting Poisson noise sources to neural populations for stimulation", + ) + delays = RandomDistribution("uniform", d_range) + weights = RandomDistribution("uniform", w_range) weight_clues = RandomDistribution("uniform", w_clues) for stimulus in range(self.n_populations): for variable in range(self.variables_number): counter = 0 if variable in self.clues[0]: - shift = self.clues[1][self.clues[0].index(variable)] * self.core_size - connections = [(m, n + shift, weight_clues.next(), delays.next()) for m in range(self.core_size) for - n in range(self.clue_size)] - synapses = p.Projection(self.clues_stim[counter], self.var_pops[variable], - p.FromListConnector(connections, safe=True), target='excitatory') + shift = ( + self.clues[1][self.clues[0].index(variable)] * self.core_size + ) + connections = [ + (m, n + shift, weight_clues.next(), delays.next()) + for m in range(self.core_size) + for n in range(self.clue_size) + ] + synapses = p.Projection( + self.clues_stim[counter], + self.var_pops[variable], + p.FromListConnector(connections, safe=True), + receptor_type="excitatory", + ) counter += 1 self.stim_conns.append(synapses) else: - synapses = p.Projection(self.stim_pops[stimulus][variable], self.var_pops[variable], - p.OneToOneConnector(weights=weights, delays=delays), target='excitatory') + synapses = p.Projection( + self.stim_pops[stimulus][variable], + self.var_pops[variable], + p.OneToOneConnector(), + synapse_type=p.StaticSynapse( + weight=weights, delay=delays.next() + ), + receptor_type="excitatory", + ) self.stim_conns.append(synapses) self.stim_times += self.stims @@ -318,19 +442,35 @@ def depress_cores(self, w_range=[-2.0, -1.5], d_range=[2.0, 2.0]): w_range: range for the random distribution of synaptic weights in the form [w_min, w_max]. d_range: range for the random distribution of synaptic delays in the form [d_min, d_max]. """ - print msg, 'connecting Poisson noise sources to neural populations for dissipation' - delays = RandomDistribution('uniform', d_range) - weights = RandomDistribution('uniform', w_range) + print( + msg, + "connecting Poisson noise sources to neural populations for dissipation", + ) + delays = RandomDistribution("uniform", d_range) + weights = RandomDistribution("uniform", w_range) for depressor in range(self.d_populations): for variable in range(self.variables_number): if variable not in self.clues[0]: - synapses = p.Projection(self.diss_pops[depressor][variable], self.var_pops[variable], - p.OneToOneConnector(weights=weights, delays=delays), target='inhibitory') + synapses = p.Projection( + self.diss_pops[depressor][variable], + self.var_pops[variable], + p.OneToOneConnector(), + synapse_type=p.StaticSynapse( + weight=weights, delay=delays.next() + ), + receptor_type="inhibitory", + ) self.diss_conns.append(synapses) self.diss_times += self.disss - def apply_constraints(self, kind="inhibitory", w_range=[-0.2, -0.0], d_range=[2.0, 2.0], random_cons=False, - pAF=0.5): + def apply_constraints( + self, + kind="inhibitory", + w_range=[-0.2, -0.0], + d_range=[2.0, 2.0], + random_cons=False, + pAF=0.5, + ): """Map constraints list to inhibitory or excitatory connections between neural populations. The clues_inhibition class variable determines whether clues should receive inhibitory connections or not. @@ -342,180 +482,271 @@ def apply_constraints(self, kind="inhibitory", w_range=[-0.2, -0.0], d_range=[2. random_cons: whether constraints are randomly choosen to be inhibitory or excitatory with probability pAF. pAF: probability of inhibitory connections, as a probability it should be between 0.0 and 1.0. It only works when random_cons is True. - """ - delays = RandomDistribution('uniform', d_range) - weights = RandomDistribution('uniform', w_range) # 1.8 2.0 spin_system - if 'weight' in self.constraints[0]: - print msg, '''creating constraints between CSP variables with specified weights and randomly distributed - delays''' + """ + delays = RandomDistribution("uniform", d_range) + weights = RandomDistribution("uniform", w_range) # 1.8 2.0 spin_system + if "weight" in self.constraints[0]: + print( + msg, + """creating constraints between CSP variables with specified weights and randomly distributed delays""", + ) else: - print msg, '''creating constraints between CSP variables with random and uniformelly distributed delays - and weights''' + print( + msg, + """creating constraints between CSP variables with random and uniformelly distributed delays and weights""", + ) for constraint in self.constraints: - source = constraint['source'] - target = constraint['target'] + source = constraint["source"] + target = constraint["target"] if random_cons: - kind = np.random.choice(['inhibitory', 'excitatory'], p=[pAF, 1-pAF]) - #TODO find a way of reducing the next two conditionals, they're equal except for conditioning on target... - #TODO ... being a clue. + kind = np.random.choice(["inhibitory", "excitatory"], p=[pAF, 1 - pAF]) + # TODO find a way of reducing the next two conditionals, they're equal except for conditioning on target... + # TODO ... being a clue. if self.clues_inhibition: - connections = [] - for n in range(self.size): - for m in range(self.size): - if 'weight' in constraint: - weight = constraint['weight'] - else: - weight = weights.next() - connections.append( - (m, n, weight if m // self.core_size == n // self.core_size else 0.0, delays.next())) - synapses = p.Projection(self.var_pops[source], self.var_pops[target], - p.FromListConnector(connections, safe=True), target=kind) + # connections = [] + connections = [ + [ + m, + n, + ( + constraint["weight"] + if "weight" in constraint + else weights.next() + ) + if m // self.core_size == n // self.core_size + else 0.0, + delays.next(), + ] + for n in range(self.size) + for m in range(self.size) + ] + # for n in range(self.size): + # for m in range(self.size): + # if "weight" in constraint: + # weight = constraint["weight"] + # else: + # weight = weights.next() + # connections.append( + # ( + # m, + # n, + # weight + # if m // self.core_size == n // self.core_size + # else 0.0, + # delays.next(), + # ) + # ) + synapses = p.Projection( + self.var_pops[source], + self.var_pops[target], + p.FromListConnector(connections, safe=True), + receptor_type=kind, + ) self.constraint_conns.append(synapses) if self.directed == False: - synapses = p.Projection(self.var_pops[target], self.var_pops[source], - p.FromListConnector(connections, safe=True), target=kind) + synapses = p.Projection( + self.var_pops[target], + self.var_pops[source], + p.FromListConnector(connections, safe=True), + receptor_type=kind, + ) self.constraint_conns.append(synapses) elif target not in self.clues[0]: - connections = [] - for n in range(self.size): - for m in range(self.size): - if 'weight' in constraint: - weight = constraint['weight'] - else: - weight = weights.next() - connections.append( - (m, n, weight if m // self.core_size == n // self.core_size else 0.0, delays.next())) - synapses = p.Projection(self.var_pops[source], self.var_pops[target], - p.FromListConnector(connections, safe=True), target=kind) + # connections = [] + connections = [ + [ + m, + n, + ( + constraint["weight"] + if "weight" in constraint + else weights.next() + ) + if m // self.core_size == n // self.core_size + else 0.0, + delays.next(), + ] + for n in range(self.size) + for m in range(self.size) + ] + # for n in range(self.size): + # for m in range(self.size): + # if "weight" in constraint: + # weight = constraint["weight"] + # else: + # weight = weights.next() + # connections.append( + # ( + # m, + # n, + # weight + # if m // self.core_size == n // self.core_size + # else 0.0, + # delays.next(), + # ) + # ) + synapses = p.Projection( + self.var_pops[source], + self.var_pops[target], + p.FromListConnector(connections, safe=True), + receptor_type=kind, + ) self.constraint_conns.append(synapses) if self.directed == False: - synapses = p.Projection(self.var_pops[target], self.var_pops[source], - p.FromListConnector(connections, safe=True), target=kind) + synapses = p.Projection( + self.var_pops[target], + self.var_pops[source], + p.FromListConnector(connections, safe=True), + receptor_type=kind, + ) self.constraint_conns.append(synapses) - def initialize(self, v_range=[-65.0, -55.0]): """Randomly initialize the membrane voltage of the neurons in the range v_range. args: v_range: range for the random distribution of membrane potentials in the form [v_min, v_max]. """ - print msg, 'randomly setting the initial voltage for each variable population' + print(msg, "randomly setting the initial voltage for each variable population") for variable in self.var_pops: - variable.initialize("v", RandomDistribution("uniform", v_range)) + initial_voltage = RandomDistribution("uniform", [-65.0, -55.0]) + variable.initialize(v=initial_voltage) def recording(self): """Record spikes from neural populations representing CSP variables. - - If live class variable is set to True this method also activate live output for the neural populations + + If live class variable is set to True this method also activate live output for the neural populations representing CSP variables. """ - print msg, 'activating recording for variable populations' + print(msg, "activating recording for variable populations") for population in self.var_pops: - population.record() + population.record("spikes") # Activate live output to be used for example with the Sudoku visualiser. if self.live: - print 'activating live output' + print("activating live output") p.external_devices.activate_live_output_for(self.var_pops) def record_stimulation(self): """Record spikes from stimulating noise sources.""" - print msg, 'activating recording for noise populations' + print(msg, "activating recording for noise populations") for stimulus in self.stim_pops: for population in stimulus: - population.record() + population.record("spikes") def record_dissipation(self): """Record spikes from depressing noise sources.""" - print msg, 'activating recording for dissipation populations' + print(msg, "activating recording for dissipation populations") for depressor in self.diss_pops: - depressor.record() + depressor.record("spikes") def save(self, filename, DAT=False): """Save spikes recorded from neural populations representing CSP variables. - - The recording() method should be called first in order to make spikes available for saving. All files will be + + The recording() method should be called first in order to make spikes available for saving. All files will be saved into the results folder. - + args: - filename: prefix of the file name where spikes will be saved in binary format. The full name will be: + filename: prefix of the file name where spikes will be saved in binary format. The full name will be: filename_spikes_binary. DAT: whether spikes should be saved also in .dat format on an additional file. """ - if not os.path.exists('results'): - os.makedirs('results') + if not os.path.exists("results"): + os.makedirs("results") if DAT: - print msg, 'saving spikes from CSP variables to file results/%s_variable#.dat' % filename + print( + msg, + "saving spikes from CSP variables to file results/%s_variable#.dat" + % filename, + ) for var_index, population in enumerate(self.var_pops): - population.printSpikes('results/%s_variable%d.dat' % (filename, var_index)) - with open('results/%s_spikes_binary' % filename, 'w+') as file: - # TODO refactor params as dictionary with *kwargs - params = [self.run_time, - self.variables_number, - self.size, - self.domain_size, - self.core_size, - self.constraints, - self.stim_times, - self.diss_times] - np.save(file, params) - for population in self.var_pops: - spikes = population.getSpikes() - np.save(file, spikes) + population.printSpikes( + "results/%s_variable%d.dat" % (filename, var_index) + ) + # with open("results/%s_spikes_binary" % filename, "w+") as file: + # TODO refactor params as dictionary with *kwargs + filepath = "results/%s_spikes_binary" % filename + params = [ + self.run_time, + self.variables_number, + self.size, + self.domain_size, + self.core_size, + self.constraints, + self.stim_times, + self.diss_times, + ] + spikes = [] + for population in self.var_pops: + spikes.append(population.get_data("spikes")) + np.savez(filepath, params=params, *spikes) self.spikes_file = filename def save_stimulation(self, filename, DAT=False): """Save spikes recorded from stimulating noise sources. The record_stimulation() method should be called first in order to make spikes available for saving. - + args: - filename: prefix of the file name where spikes will be saved in .dat format. The full name will be: + filename: prefix of the file name where spikes will be saved in .dat format. The full name will be: stim_#_filename_variables#.dat. DAT: whether spikes should be saved also in .dat format on an additional file. """ if DAT: for pulse_index, pulse in enumerate(self.stim_pops): - print msg, 'saving spikes from noise sources to file results/stim_%d_%s.dat' % (pulse_index, filename) + print( + msg, + "saving spikes from noise sources to file results/stim_%d_%s.dat" + % (pulse_index, filename), + ) for var_index, population in enumerate(pulse): - population.printSpikes('results/stim_%d_%s_variables%d.dat' % (pulse_index, filename, var_index)) - with open('results/%s_stim_spikes_binary' % filename, 'w+') as file: - for pulse in self.stim_pops: - for population in pulse: - spikes = population.getSpikes() - np.save(file, spikes) + population.printSpikes( + "results/stim_%d_%s_variables%d.dat" + % (pulse_index, filename, var_index) + ) + filepath = "results/%s_stim_spikes_binary" % filename + for pulse in self.stim_pops: + for population in pulse: + spikes = population.getSpikes() + np.save(filepath, spikes) def save_dissipation(self, filename, DAT=False): """Save spikes recorded from depressing noise sources. The record_dissipation() method should be called first in order to make spikes available for saving. - + args: - filename: prefix of the file name where spikes will be saved in .dat format. The full name will be: + filename: prefix of the file name where spikes will be saved in .dat format. The full name will be: diss_#_filename_variables#.dat. DAT: whether spikes should be saved also in .dat format on an additional file. """ if DAT: for pulse_index, pulse in enumerate(self.diss_pops): - print msg, 'saving dissipation to file results/diss_%d_%s.dat' % (pulse_index, filename) + print( + msg, + "saving dissipation to file results/diss_%d_%s.dat" + % (pulse_index, filename), + ) for var_index, population in enumerate(pulse): - population.printSpikes('results/diss_%d_%s_variable%d.dat' % (pulse_index, filename, var_index)) - with open('results/%s_diss_spikes_binary' % filename, 'w+') as file: - for pulse in self.diss_pops: - for population in pulse: - spikes = population.getSpikes() - np.save(file, spikes) + population.printSpikes( + "results/diss_%d_%s_variable%d.dat" + % (pulse_index, filename, var_index) + ) + filepath = "results/%s_diss_spikes_binary" % filename + for pulse in self.diss_pops: + for population in pulse: + spikes = population.getSpikes() + np.save(filepath, spikes) def report_network_params(self, filename=False): - """ Report the network dimensions and parameters to standard output or file. - + """Report the network dimensions and parameters to standard output or file. + args: filename: name of file in which report will be saved. if not specified the report will show up only in the standard output. """ # Count populations. - if not os.path.exists('results'): - os.makedirs('results') + if not os.path.exists("results"): + os.makedirs("results") var_pops_num = len(self.var_pops) diss_pops_num = len(self.diss_pops) stim_pops_num = len(self.stim_pops) * len(self.stim_pops[0]) @@ -528,16 +759,16 @@ def report_network_params(self, filename=False): # Create function to count synapses from projections list. def projections_counter(projections_pop): - """ Count synapses from a list of projections. + """Count synapses from a list of projections. args: - projections_pop: list of projections, collected when calling other methods. - + projections_pop: list of projections, collected when calling other methods. + returns: synapse_number: the number of synapses generated by the list of projections. """ # synapse_number = sum(map(lambda x: len(x.getWeights('list').flatten()), projections_pop)) - synapse_number = lambda x: x.getWeights('list').flatten() + synapse_number = lambda x: x.get(["weight"], "list").flatten() synapse_number = map(synapse_number, projections_pop) synapse_number = map(len, synapse_number) synapse_number = sum(synapse_number) @@ -549,36 +780,53 @@ def projections_counter(projections_pop): stim_conns = projections_counter(self.stim_conns) diss_conns = projections_counter(self.diss_conns) constraint_conns = projections_counter(self.constraint_conns) - net_conns = sum([core_conns, internal_conns, stim_conns, diss_conns, constraint_conns]) + net_conns = sum( + [core_conns, internal_conns, stim_conns, diss_conns, constraint_conns] + ) # Report template. - report = """ + report = """ |======== Network Parameters =========| |_____________________________________| - |Total number of neurons: %d - | variables neurons: %d - | stimulation neurons: %d - | dissipation neurons: %d - |_____________________________________ - |Total number of populations: %d - | variables populations: %d - | dissipation populations: %d + |Total number of neurons: %d + | variables neurons: %d + | stimulation neurons: %d + | dissipation neurons: %d + |_____________________________________ + |Total number of populations: %d + | variables populations: %d + | dissipation populations: %d | stimulation poppulations: %d - | domain size: %d - | core size: %d + | domain size: %d + | core size: %d |_____________________________________ - |Total number of synapses: %d - | stimulating synapses: %d - | dissipating synapses: %d - | constraints synapses: %d + |Total number of synapses: %d + | stimulating synapses: %d + | dissipating synapses: %d + | constraints synapses: %d | var internal synapses: %d - | core internal synapses: %d + | core internal synapses: %d |=====================================| - """ % (net_neurons, var_neurons, stim_neurons, diss_neurons, pops_number, var_pops_num, - diss_pops_num, stim_pops_num, self.domain_size, self.core_size, net_conns, - stim_conns, diss_conns, constraint_conns, internal_conns, core_conns) - print report + """ % ( + net_neurons, + var_neurons, + stim_neurons, + diss_neurons, + pops_number, + var_pops_num, + diss_pops_num, + stim_pops_num, + self.domain_size, + self.core_size, + net_conns, + stim_conns, + diss_conns, + constraint_conns, + internal_conns, + core_conns, + ) + print(report) # Print report to file. if filename: - with open('results/%s.dat' % filename, 'w+') as file: - file.write(report) \ No newline at end of file + with open("results/%s.dat" % filename, "w+") as file: + file.write(report) diff --git a/spinnaker_csp/translators/spin2csp.py b/spinnaker_csp/translators/spin2csp.py index 4a70c32..ad79915 100644 --- a/spinnaker_csp/translators/spin2csp.py +++ b/spinnaker_csp/translators/spin2csp.py @@ -3,31 +3,37 @@ # # "Using Stochastic Spiking Neural Networks on SpiNNaker to Solve Constraint Satisfaction Problems" # Submitted to the journal Frontiers in Neuroscience| Neuromorphic Engineering -#----------------------------------------------------------------------------------------------------------------------- +# ----------------------------------------------------------------------------------------------------------------------- """Class that defines a constraint satisfaction problem representing a lattice of Ising spins. -This module contains the spin2csp class which allows the definition of 1-dimensional, 2-dimensional or 3-dimensional -regular lattices where nodes represent Ising spins which interact ferromagnetically or antiferromagnetically with their -nearest-neighbours. The problem is described as a constraint satisfaction problem in which variables are numbered +This module contains the spin2csp class which allows the definition of 1-dimensional, 2-dimensional or 3-dimensional +regular lattices where nodes represent Ising spins which interact ferromagnetically or antiferromagnetically with their +nearest-neighbours. The problem is described as a constraint satisfaction problem in which variables are numbered """ import numpy as np -class spin2csp: +class spin2csp: def __init__(self, length, dimensions=3): chain = range(length**dimensions) - lattice = np.reshape(chain, tuple([length]*dimensions)) + lattice = np.reshape(chain, tuple([length] * dimensions)) constraints = [] - if dimensions==3: + if dimensions == 3: for x in range(length): for y in range(length): for z in range(length): - if x != length-1: constraints.append((lattice[x][y][z], lattice[x+1][y][z])) - if y != length-1: constraints.append((lattice[x][y][z], lattice[x][y+1][z])) - if z != length-1: constraints.append((lattice[x][y][z], lattice[x][y][z+1])) - if x >= 0: constraints.append((lattice[x][y][z], lattice[x-1][y][z])) - if y >= 0: constraints.append((lattice[x][y][z], lattice[x][y-1][z])) - if z >= 0: constraints.append((lattice[x][y][z], lattice[x][y][z-1])) + if x != length - 1: + constraints.append((lattice[x][y][z], lattice[x + 1][y][z])) + if y != length - 1: + constraints.append((lattice[x][y][z], lattice[x][y + 1][z])) + if z != length - 1: + constraints.append((lattice[x][y][z], lattice[x][y][z + 1])) + if x >= 0: + constraints.append((lattice[x][y][z], lattice[x - 1][y][z])) + if y >= 0: + constraints.append((lattice[x][y][z], lattice[x][y - 1][z])) + if z >= 0: + constraints.append((lattice[x][y][z], lattice[x][y][z - 1])) # Create periodic condition on boundaries: constraints.append((lattice[x][y][length - 1], lattice[x][y][0])) constraints.append((lattice[x][length - 1][y], lattice[x][0][y])) @@ -35,26 +41,33 @@ def __init__(self, length, dimensions=3): elif dimensions == 2: for x in range(length): for y in range(length): - if x != length - 1: constraints.append((lattice[x][y], lattice[x + 1][y])) - if x >= 0: constraints.append((lattice[x][y], lattice[x - 1][y])) - if y != length - 1: constraints.append((lattice[x][y], lattice[x][y + 1])) - if y >= 0: constraints.append((lattice[x][y], lattice[x][y - 1])) + if x != length - 1: + constraints.append((lattice[x][y], lattice[x + 1][y])) + if x >= 0: + constraints.append((lattice[x][y], lattice[x - 1][y])) + if y != length - 1: + constraints.append((lattice[x][y], lattice[x][y + 1])) + if y >= 0: + constraints.append((lattice[x][y], lattice[x][y - 1])) # Create periodic condition on boundaries: constraints.append((lattice[x][length - 1], lattice[x][0])) constraints.append((lattice[length - 1][x], lattice[0][x])) - else: # dimensions == 1: + else: # dimensions == 1: for x in range(length): - if x != length - 1: constraints.append((lattice[x], lattice[x + 1])) - if x >= 0: constraints.append((lattice[x], lattice[x - 1])) + if x != length - 1: + constraints.append((lattice[x], lattice[x + 1])) + if x >= 0: + constraints.append((lattice[x], lattice[x - 1])) # Create periodic condition on boundaries: - constraints.append((lattice[length-1],lattice[0])) + constraints.append((lattice[length - 1], lattice[0])) formatted_constraints = [] for constraint in constraints: - formatted_constraints.append({'source': constraint[0], 'target': constraint[1]}) + formatted_constraints.append( + {"source": constraint[0], "target": constraint[1]} + ) self.array = lattice self.var_num = length**dimensions self.constraints = formatted_constraints - self.cons_num = len(constraints) # it should equal (length**3)*6-(length**2)*6 + self.cons_num = len(constraints) # it should equal (length**3)*6-(length**2)*6 self.dom_num = 2 - diff --git a/spinnaker_csp/translators/sudoku2csp.py b/spinnaker_csp/translators/sudoku2csp.py index 5a6bba4..2983471 100644 --- a/spinnaker_csp/translators/sudoku2csp.py +++ b/spinnaker_csp/translators/sudoku2csp.py @@ -3,7 +3,7 @@ # # "Using Stochastic Spiking Neural Networks on SpiNNaker to Solve Constraint Satisfaction Problems" # Submitted to the journal Frontiers in Neuroscience| Neuromorphic Engineering -#----------------------------------------------------------------------------------------------------------------------- +# ----------------------------------------------------------------------------------------------------------------------- """Class that translates Sudoku puzzles to constraint satisfaction problems. This module contains the sudoku2csp class which accept puzzles formated as a python 9x9 array of the form: @@ -20,43 +20,55 @@ in the previous grid there are no clues, you can change the zeros for appropriate clues. """ + + class sudoku2csp: """Class that translates Sudoku puzzles to constraint satisfaction problems. - - From the 9x9 arrat CSP variables are defined enumerating from leftmost digit of the first (top) row to the rightmost - of the last (bottom) row. Constraints are defined in a directed way so the snn_creator should build the other half - from symmetry arguments. + + From the 9x9 arrat CSP variables are defined enumerating from leftmost digit of the first (top) row to the rightmost + of the last (bottom) row. Constraints are defined in a directed way so the snn_creator should build the other half + from symmetry arguments. """ + def __init__(self, clue_list=[]): """Initialize the sudoku2csp class. - + args: clue_list: a python 9x9 array representing the Sudoku puzzle, blank cells are given with the digit 0. """ # Define CSP variables for Sudoku, - variables=[] + variables = [] size = len(clue_list) - sub_size = int(size**(1./2.)) + sub_size = int(size ** (1.0 / 2.0)) for i in range(size): for j in range(size): - variables.append([i,j]) + variables.append([i, j]) # Define CSP constraints for Sudoku. constraints = [] # Horizontal and vertical constraints. for var1, xy1 in enumerate(variables): for var2, xy2 in enumerate(variables): - if (xy2[0] == xy1[0] or xy2[1] == xy1[1]) and var2>var1: - constraints.append([var1,var2]) + if (xy2[0] == xy1[0] or xy2[1] == xy1[1]) and var2 > var1: + constraints.append([var1, var2]) # 3x3 squares diagonal constraints. for var1, xy1 in enumerate(variables): for var2, xy2 in enumerate(variables): # below: same 3X3 square & (different row & different column) & var2>var1 - if (xy2[0]//3 == xy1[0]//3 and xy2[1]//sub_size == xy1[1]//sub_size) and (xy2[0]!=xy1[0] and xy2[1]!=xy1[1]) and var2>var1: - constraints.append([var1,var2]) + if ( + ( + xy2[0] // 3 == xy1[0] // 3 + and xy2[1] // sub_size == xy1[1] // sub_size + ) + and (xy2[0] != xy1[0] and xy2[1] != xy1[1]) + and var2 > var1 + ): + constraints.append([var1, var2]) # Apply format to constraints. formatted_constraints = [] for constraint in constraints: - formatted_constraints.append({'source': constraint[0], 'target': constraint[1]}) + formatted_constraints.append( + {"source": constraint[0], "target": constraint[1]} + ) # Format clues for set_clues method in CSP class. if clue_list: # flatten puzzle input @@ -70,20 +82,19 @@ def __init__(self, clue_list=[]): for i, clue in enumerate(digits): if clue != 0: clues[0].append(i) - clues[1].append(clue-1) - self.variables = variables + clues[1].append(clue - 1) + self.variables = variables self.constraints = formatted_constraints - self.var_num = len(variables) - self.cons_num = len(constraints) - self.dom_num = size - self.clues = clues + self.var_num = len(variables) + self.cons_num = len(constraints) + self.dom_num = size + self.clues = clues def var_grid(self): """Print the CSP variables in a 9X9 square format""" - n=0 + n = 0 for i in range(9): - print(self.variables[n:n+9]) - n+=9 - print('\n Total number of variables: %d'%(len(self.variables))) - print('\n Total number of constraints: %d'%(len(self.constraints))) - + print(self.variables[n : n + 9]) + n += 9 + print("\n Total number of variables: %d" % (len(self.variables))) + print("\n Total number of constraints: %d" % (len(self.constraints))) diff --git a/spinnaker_csp/translators/world_bordering_countries.py b/spinnaker_csp/translators/world_bordering_countries.py index 5da3a5d..c692e1c 100644 --- a/spinnaker_csp/translators/world_bordering_countries.py +++ b/spinnaker_csp/translators/world_bordering_countries.py @@ -3,821 +3,825 @@ # # "Using Stochastic Spiking Neural Networks on SpiNNaker to Solve Constraint Satisfaction Problems" # Submitted to the journal Frontiers in Neuroscience| Neuromorphic Engineering -#----------------------------------------------------------------------------------------------------------------------- +# ----------------------------------------------------------------------------------------------------------------------- """Define a CSP representation of the map of the world. -Countries are represented by numbers acording to the list in alphabetical order as seen in the world_countries -dictionary. Constraints corresponding to bordering countries of the world as defined by the United Nations as shown in -the world_borders dictionary. +Countries are represented by numbers acording to the list in alphabetical order as seen in the world_countries +dictionary. Constraints corresponding to bordering countries of the world as defined by the United Nations as shown in +the world_borders dictionary. The countries and bordering correspond to data from the United Nations available in Mathematica Wolfram -(Wolfram Research, 2017). +(Wolfram Research, 2017). """ -world_countries=[{"Afghanistan":0}, -{"Albania":1}, -{"Algeria":2}, -{"Andorra":3}, -{"Angola":4}, -{"Antigua and Barbuda":5}, -{"Argentina":6}, -{"Armenia":7}, -{"Australia":8}, -{"Austria":9}, -{"Azerbaijan":10}, -{"Bahamas":11}, -{"Bahrain":12}, -{"Bangladesh":13}, -{"Barbados":14}, -{"Belarus":15}, -{"Belgium":16}, -{"Belize":17}, -{"Benin":18}, -{"Bhutan":19}, -{"Bolivia":20}, -{"Bosnia and Herzegovina":21}, -{"Botswana":22}, -{"Brazil":23}, -{"Brunei":24}, -{"Bulgaria":25}, -{"Burkina Faso":26}, -{"Burundi":27}, -{"Cambodia":28}, -{"Cameroon":29}, -{"Canada":30}, -{"Cape Verde":31}, -{"Central African Republic":32}, -{"Chad":33}, -{"Chile":34}, -{"China":35}, -{"Colombia":36}, -{"Comoros":37}, -{"Costa Rica":38}, -{"Croatia":39}, -{"Cuba":40}, -{"Cyprus":41}, -{"Czech Republic":42}, -{"Democratic Republic of the Congo":43}, -{"Denmark":44}, -{"Djibouti":45}, -{"Dominica":46}, -{"Dominican Republic":47}, -{"East Timor":48}, -{"Ecuador":49}, -{"Egypt":50}, -{"El Salvador":51}, -{"Equatorial Guinea":52}, -{"Eritrea":53}, -{"Estonia":54}, -{"Ethiopia":55}, -{"Fiji":56}, -{"Finland":57}, -{"France":58}, -{"Gabon":59}, -{"Gambia":60}, -{"Georgia":61}, -{"Germany":62}, -{"Ghana":63}, -{"Greece":64}, -{"Grenada":65}, -{"Guatemala":66}, -{"Guinea":67}, -{"Guinea-Bissau":68}, -{"Guyana":69}, -{"Haiti":70}, -{"Honduras":71}, -{"Hungary":72}, -{"Iceland":73}, -{"India":74}, -{"Indonesia":75}, -{"Iran":76}, -{"Iraq":77}, -{"Ireland":78}, -{"Israel":79}, -{"Italy":80}, -{"Ivory Coast":81}, -{"Jamaica":82}, -{"Japan":83}, -{"Jordan":84}, -{"Kazakhstan":85}, -{"Kenya":86}, -{"Kiribati":87}, -{"Kuwait":88}, -{"Kyrgyzstan":89}, -{"Laos":90}, -{"Latvia":91}, -{"Lebanon":92}, -{"Lesotho":93}, -{"Liberia":94}, -{"Libya":95}, -{"Liechtenstein":96}, -{"Lithuania":97}, -{"Luxembourg":98}, -{"Macedonia":99}, -{"Madagascar":100}, -{"Malawi":101}, -{"Malaysia":102}, -{"Maldives":103}, -{"Mali":104}, -{"Malta":105}, -{"Marshall Islands":106}, -{"Mauritania":107}, -{"Mauritius":108}, -{"Mexico":109}, -{"Micronesia":110}, -{"Moldova":111}, -{"Monaco":112}, -{"Mongolia":113}, -{"Montenegro":114}, -{"Morocco":115}, -{"Mozambique":116}, -{"Myanmar":117}, -{"Namibia":118}, -{"Nauru":119}, -{"Nepal":120}, -{"Netherlands":121}, -{"New Zealand":122}, -{"Nicaragua":123}, -{"Niger":124}, -{"Nigeria":125}, -{"North Korea":126}, -{"Norway":127}, -{"Oman":128}, -{"Pakistan":129}, -{"Palau":130}, -{"Panama":131}, -{"Papua New Guinea":132}, -{"Paraguay":133}, -{"Peru":134}, -{"Philippines":135}, -{"Poland":136}, -{"Portugal":137}, -{"Qatar":138}, -{"Republic of the Congo":139}, -{"Romania":140}, -{"Russia":141}, -{"Rwanda":142}, -{"Saint Kitts and Nevis":143}, -{"Saint Lucia":144}, -{"Saint Vincent and the Grenadines":145}, -{"Samoa":146}, -{"San Marino":147}, -{"Sao Tome and Principe":148}, -{"Saudi Arabia":149}, -{"Senegal":150}, -{"Serbia":151}, -{"Seychelles":152}, -{"Sierra Leone":153}, -{"Singapore":154}, -{"Slovakia":155}, -{"Slovenia":156}, -{"Solomon Islands":157}, -{"Somalia":158}, -{"South Africa":159}, -{"South Korea":160}, -{"South Sudan":161}, -{"Spain":162}, -{"Sri Lanka":163}, -{"Sudan":164}, -{"Suriname":165}, -{"Swaziland":166}, -{"Sweden":167}, -{"Switzerland":168}, -{"Syria":169}, -{"Tajikistan":170}, -{"Tanzania":171}, -{"Thailand":172}, -{"Togo":173}, -{"Tonga":174}, -{"Trinidad and Tobago":175}, -{"Tunisia":176}, -{"Turkey":177}, -{"Turkmenistan":178}, -{"Tuvalu":179}, -{"Uganda":180}, -{"Ukraine":181}, -{"United Arab Emirates":182}, -{"United Kingdom":183}, -{"United States":184}, -{"Uruguay":185}, -{"Uzbekistan":186}, -{"Vanuatu":187}, -{"Venezuela":188}, -{"Vietnam":189}, -{"Yemen":190}, -{"Zambia":191}, -{"Zimbabwe":192}] +world_countries = [ + {"Afghanistan": 0}, + {"Albania": 1}, + {"Algeria": 2}, + {"Andorra": 3}, + {"Angola": 4}, + {"Antigua and Barbuda": 5}, + {"Argentina": 6}, + {"Armenia": 7}, + {"Australia": 8}, + {"Austria": 9}, + {"Azerbaijan": 10}, + {"Bahamas": 11}, + {"Bahrain": 12}, + {"Bangladesh": 13}, + {"Barbados": 14}, + {"Belarus": 15}, + {"Belgium": 16}, + {"Belize": 17}, + {"Benin": 18}, + {"Bhutan": 19}, + {"Bolivia": 20}, + {"Bosnia and Herzegovina": 21}, + {"Botswana": 22}, + {"Brazil": 23}, + {"Brunei": 24}, + {"Bulgaria": 25}, + {"Burkina Faso": 26}, + {"Burundi": 27}, + {"Cambodia": 28}, + {"Cameroon": 29}, + {"Canada": 30}, + {"Cape Verde": 31}, + {"Central African Republic": 32}, + {"Chad": 33}, + {"Chile": 34}, + {"China": 35}, + {"Colombia": 36}, + {"Comoros": 37}, + {"Costa Rica": 38}, + {"Croatia": 39}, + {"Cuba": 40}, + {"Cyprus": 41}, + {"Czech Republic": 42}, + {"Democratic Republic of the Congo": 43}, + {"Denmark": 44}, + {"Djibouti": 45}, + {"Dominica": 46}, + {"Dominican Republic": 47}, + {"East Timor": 48}, + {"Ecuador": 49}, + {"Egypt": 50}, + {"El Salvador": 51}, + {"Equatorial Guinea": 52}, + {"Eritrea": 53}, + {"Estonia": 54}, + {"Ethiopia": 55}, + {"Fiji": 56}, + {"Finland": 57}, + {"France": 58}, + {"Gabon": 59}, + {"Gambia": 60}, + {"Georgia": 61}, + {"Germany": 62}, + {"Ghana": 63}, + {"Greece": 64}, + {"Grenada": 65}, + {"Guatemala": 66}, + {"Guinea": 67}, + {"Guinea-Bissau": 68}, + {"Guyana": 69}, + {"Haiti": 70}, + {"Honduras": 71}, + {"Hungary": 72}, + {"Iceland": 73}, + {"India": 74}, + {"Indonesia": 75}, + {"Iran": 76}, + {"Iraq": 77}, + {"Ireland": 78}, + {"Israel": 79}, + {"Italy": 80}, + {"Ivory Coast": 81}, + {"Jamaica": 82}, + {"Japan": 83}, + {"Jordan": 84}, + {"Kazakhstan": 85}, + {"Kenya": 86}, + {"Kiribati": 87}, + {"Kuwait": 88}, + {"Kyrgyzstan": 89}, + {"Laos": 90}, + {"Latvia": 91}, + {"Lebanon": 92}, + {"Lesotho": 93}, + {"Liberia": 94}, + {"Libya": 95}, + {"Liechtenstein": 96}, + {"Lithuania": 97}, + {"Luxembourg": 98}, + {"Macedonia": 99}, + {"Madagascar": 100}, + {"Malawi": 101}, + {"Malaysia": 102}, + {"Maldives": 103}, + {"Mali": 104}, + {"Malta": 105}, + {"Marshall Islands": 106}, + {"Mauritania": 107}, + {"Mauritius": 108}, + {"Mexico": 109}, + {"Micronesia": 110}, + {"Moldova": 111}, + {"Monaco": 112}, + {"Mongolia": 113}, + {"Montenegro": 114}, + {"Morocco": 115}, + {"Mozambique": 116}, + {"Myanmar": 117}, + {"Namibia": 118}, + {"Nauru": 119}, + {"Nepal": 120}, + {"Netherlands": 121}, + {"New Zealand": 122}, + {"Nicaragua": 123}, + {"Niger": 124}, + {"Nigeria": 125}, + {"North Korea": 126}, + {"Norway": 127}, + {"Oman": 128}, + {"Pakistan": 129}, + {"Palau": 130}, + {"Panama": 131}, + {"Papua New Guinea": 132}, + {"Paraguay": 133}, + {"Peru": 134}, + {"Philippines": 135}, + {"Poland": 136}, + {"Portugal": 137}, + {"Qatar": 138}, + {"Republic of the Congo": 139}, + {"Romania": 140}, + {"Russia": 141}, + {"Rwanda": 142}, + {"Saint Kitts and Nevis": 143}, + {"Saint Lucia": 144}, + {"Saint Vincent and the Grenadines": 145}, + {"Samoa": 146}, + {"San Marino": 147}, + {"Sao Tome and Principe": 148}, + {"Saudi Arabia": 149}, + {"Senegal": 150}, + {"Serbia": 151}, + {"Seychelles": 152}, + {"Sierra Leone": 153}, + {"Singapore": 154}, + {"Slovakia": 155}, + {"Slovenia": 156}, + {"Solomon Islands": 157}, + {"Somalia": 158}, + {"South Africa": 159}, + {"South Korea": 160}, + {"South Sudan": 161}, + {"Spain": 162}, + {"Sri Lanka": 163}, + {"Sudan": 164}, + {"Suriname": 165}, + {"Swaziland": 166}, + {"Sweden": 167}, + {"Switzerland": 168}, + {"Syria": 169}, + {"Tajikistan": 170}, + {"Tanzania": 171}, + {"Thailand": 172}, + {"Togo": 173}, + {"Tonga": 174}, + {"Trinidad and Tobago": 175}, + {"Tunisia": 176}, + {"Turkey": 177}, + {"Turkmenistan": 178}, + {"Tuvalu": 179}, + {"Uganda": 180}, + {"Ukraine": 181}, + {"United Arab Emirates": 182}, + {"United Kingdom": 183}, + {"United States": 184}, + {"Uruguay": 185}, + {"Uzbekistan": 186}, + {"Vanuatu": 187}, + {"Venezuela": 188}, + {"Vietnam": 189}, + {"Yemen": 190}, + {"Zambia": 191}, + {"Zimbabwe": 192}, +] -world_borders= [{"source":0,"target":35}, -{"source":0,"target":76}, -{"source":0,"target":129}, -{"source":0,"target":170}, -{"source":0,"target":178}, -{"source":0,"target":186}, -{"source":1,"target":64}, -{"source":1,"target":99}, -{"source":1,"target":114}, -{"source":2,"target":95}, -{"source":2,"target":104}, -{"source":2,"target":107}, -{"source":2,"target":115}, -{"source":2,"target":124}, -{"source":2,"target":176}, -{"source":3,"target":58}, -{"source":3,"target":162}, -{"source":4,"target":43}, -{"source":4,"target":118}, -{"source":4,"target":139}, -{"source":4,"target":191}, -{"source":6,"target":20}, -{"source":6,"target":23}, -{"source":6,"target":34}, -{"source":6,"target":133}, -{"source":6,"target":185}, -{"source":7,"target":10}, -{"source":7,"target":61}, -{"source":7,"target":76}, -{"source":7,"target":177}, -{"source":9,"target":42}, -{"source":9,"target":62}, -{"source":9,"target":72}, -{"source":9,"target":80}, -{"source":9,"target":96}, -{"source":9,"target":155}, -{"source":9,"target":156}, -{"source":9,"target":168}, -{"source":10,"target":7}, -{"source":10,"target":61}, -{"source":10,"target":76}, -{"source":10,"target":141}, -{"source":10,"target":177}, -{"source":13,"target":74}, -{"source":13,"target":117}, -{"source":15,"target":91}, -{"source":15,"target":97}, -{"source":15,"target":136}, -{"source":15,"target":141}, -{"source":15,"target":181}, -{"source":16,"target":58}, -{"source":16,"target":62}, -{"source":16,"target":98}, -{"source":16,"target":121}, -{"source":17,"target":66}, -{"source":17,"target":109}, -{"source":18,"target":26}, -{"source":18,"target":124}, -{"source":18,"target":125}, -{"source":18,"target":173}, -{"source":19,"target":35}, -{"source":19,"target":74}, -{"source":20,"target":6}, -{"source":20,"target":23}, -{"source":20,"target":34}, -{"source":20,"target":133}, -{"source":20,"target":134}, -{"source":21,"target":39}, -{"source":21,"target":114}, -{"source":21,"target":151}, -{"source":22,"target":118}, -{"source":22,"target":159}, -{"source":22,"target":191}, -{"source":22,"target":192}, -{"source":23,"target":6}, -{"source":23,"target":20}, -{"source":23,"target":36}, -{"source":23,"target":69}, -{"source":23,"target":133}, -{"source":23,"target":134}, -{"source":23,"target":165}, -{"source":23,"target":185}, -{"source":23,"target":188}, -{"source":24,"target":102}, -{"source":25,"target":64}, -{"source":25,"target":99}, -{"source":25,"target":140}, -{"source":25,"target":151}, -{"source":25,"target":177}, -{"source":26,"target":18}, -{"source":26,"target":63}, -{"source":26,"target":81}, -{"source":26,"target":104}, -{"source":26,"target":124}, -{"source":26,"target":173}, -{"source":27,"target":43}, -{"source":27,"target":142}, -{"source":27,"target":171}, -{"source":28,"target":90}, -{"source":28,"target":172}, -{"source":28,"target":189}, -{"source":29,"target":32}, -{"source":29,"target":33}, -{"source":29,"target":52}, -{"source":29,"target":59}, -{"source":29,"target":125}, -{"source":29,"target":139}, -{"source":30,"target":184}, -{"source":32,"target":29}, -{"source":32,"target":33}, -{"source":32,"target":43}, -{"source":32,"target":139}, -{"source":32,"target":161}, -{"source":32,"target":164}, -{"source":33,"target":29}, -{"source":33,"target":32}, -{"source":33,"target":95}, -{"source":33,"target":124}, -{"source":33,"target":125}, -{"source":33,"target":164}, -{"source":34,"target":6}, -{"source":34,"target":20}, -{"source":34,"target":134}, -{"source":35,"target":0}, -{"source":35,"target":19}, -{"source":35,"target":74}, -{"source":35,"target":85}, -{"source":35,"target":89}, -{"source":35,"target":90}, -{"source":35,"target":113}, -{"source":35,"target":117}, -{"source":35,"target":120}, -{"source":35,"target":126}, -{"source":35,"target":129}, -{"source":35,"target":141}, -{"source":35,"target":170}, -{"source":35,"target":189}, -{"source":36,"target":23}, -{"source":36,"target":49}, -{"source":36,"target":131}, -{"source":36,"target":134}, -{"source":36,"target":188}, -{"source":38,"target":123}, -{"source":38,"target":131}, -{"source":39,"target":21}, -{"source":39,"target":72}, -{"source":39,"target":114}, -{"source":39,"target":151}, -{"source":39,"target":156}, -{"source":42,"target":9}, -{"source":42,"target":62}, -{"source":42,"target":136}, -{"source":42,"target":155}, -{"source":43,"target":4}, -{"source":43,"target":27}, -{"source":43,"target":32}, -{"source":43,"target":139}, -{"source":43,"target":142}, -{"source":43,"target":161}, -{"source":43,"target":171}, -{"source":43,"target":180}, -{"source":43,"target":191}, -{"source":44,"target":62}, -{"source":45,"target":53}, -{"source":45,"target":55}, -{"source":45,"target":158}, -{"source":47,"target":70}, -{"source":48,"target":75}, -{"source":49,"target":36}, -{"source":49,"target":134}, -{"source":50,"target":79}, -{"source":50,"target":95}, -{"source":50,"target":164}, -{"source":51,"target":66}, -{"source":51,"target":71}, -{"source":52,"target":29}, -{"source":52,"target":59}, -{"source":53,"target":45}, -{"source":53,"target":55}, -{"source":53,"target":164}, -{"source":54,"target":91}, -{"source":54,"target":141}, -{"source":55,"target":45}, -{"source":55,"target":53}, -{"source":55,"target":86}, -{"source":55,"target":158}, -{"source":55,"target":161}, -{"source":55,"target":164}, -{"source":57,"target":127}, -{"source":57,"target":141}, -{"source":57,"target":167}, -{"source":58,"target":3}, -{"source":58,"target":16}, -{"source":58,"target":62}, -{"source":58,"target":80}, -{"source":58,"target":98}, -{"source":58,"target":112}, -{"source":58,"target":162}, -{"source":58,"target":168}, -{"source":59,"target":29}, -{"source":59,"target":52}, -{"source":59,"target":139}, -{"source":60,"target":150}, -{"source":61,"target":7}, -{"source":61,"target":10}, -{"source":61,"target":141}, -{"source":61,"target":177}, -{"source":62,"target":9}, -{"source":62,"target":16}, -{"source":62,"target":42}, -{"source":62,"target":44}, -{"source":62,"target":58}, -{"source":62,"target":98}, -{"source":62,"target":121}, -{"source":62,"target":136}, -{"source":62,"target":168}, -{"source":63,"target":26}, -{"source":63,"target":81}, -{"source":63,"target":173}, -{"source":64,"target":1}, -{"source":64,"target":25}, -{"source":64,"target":99}, -{"source":64,"target":177}, -{"source":66,"target":17}, -{"source":66,"target":51}, -{"source":66,"target":71}, -{"source":66,"target":109}, -{"source":67,"target":68}, -{"source":67,"target":81}, -{"source":67,"target":94}, -{"source":67,"target":104}, -{"source":67,"target":150}, -{"source":67,"target":153}, -{"source":68,"target":67}, -{"source":68,"target":150}, -{"source":69,"target":23}, -{"source":69,"target":165}, -{"source":69,"target":188}, -{"source":70,"target":47}, -{"source":71,"target":51}, -{"source":71,"target":66}, -{"source":71,"target":123}, -{"source":72,"target":9}, -{"source":72,"target":39}, -{"source":72,"target":140}, -{"source":72,"target":151}, -{"source":72,"target":155}, -{"source":72,"target":156}, -{"source":72,"target":181}, -{"source":74,"target":13}, -{"source":74,"target":19}, -{"source":74,"target":35}, -{"source":74,"target":117}, -{"source":74,"target":120}, -{"source":74,"target":129}, -{"source":75,"target":48}, -{"source":75,"target":102}, -{"source":75,"target":132}, -{"source":76,"target":0}, -{"source":76,"target":7}, -{"source":76,"target":10}, -{"source":76,"target":77}, -{"source":76,"target":129}, -{"source":76,"target":177}, -{"source":76,"target":178}, -{"source":77,"target":76}, -{"source":77,"target":84}, -{"source":77,"target":88}, -{"source":77,"target":149}, -{"source":77,"target":169}, -{"source":77,"target":177}, -{"source":78,"target":183}, -{"source":79,"target":50}, -{"source":79,"target":84}, -{"source":79,"target":92}, -{"source":79,"target":169}, -{"source":80,"target":9}, -{"source":80,"target":58}, -{"source":80,"target":147}, -{"source":80,"target":156}, -{"source":80,"target":168}, -{"source":81,"target":26}, -{"source":81,"target":63}, -{"source":81,"target":67}, -{"source":81,"target":94}, -{"source":81,"target":104}, -{"source":84,"target":77}, -{"source":84,"target":79}, -{"source":84,"target":149}, -{"source":84,"target":169}, -{"source":85,"target":35}, -{"source":85,"target":89}, -{"source":85,"target":141}, -{"source":85,"target":178}, -{"source":85,"target":186}, -{"source":86,"target":55}, -{"source":86,"target":158}, -{"source":86,"target":161}, -{"source":86,"target":171}, -{"source":86,"target":180}, -{"source":88,"target":77}, -{"source":88,"target":149}, -{"source":89,"target":35}, -{"source":89,"target":85}, -{"source":89,"target":170}, -{"source":89,"target":186}, -{"source":90,"target":28}, -{"source":90,"target":35}, -{"source":90,"target":117}, -{"source":90,"target":172}, -{"source":90,"target":189}, -{"source":91,"target":15}, -{"source":91,"target":54}, -{"source":91,"target":97}, -{"source":91,"target":141}, -{"source":92,"target":79}, -{"source":92,"target":169}, -{"source":93,"target":159}, -{"source":94,"target":67}, -{"source":94,"target":81}, -{"source":94,"target":153}, -{"source":95,"target":2}, -{"source":95,"target":33}, -{"source":95,"target":50}, -{"source":95,"target":124}, -{"source":95,"target":164}, -{"source":95,"target":176}, -{"source":96,"target":9}, -{"source":96,"target":168}, -{"source":97,"target":15}, -{"source":97,"target":91}, -{"source":97,"target":136}, -{"source":97,"target":141}, -{"source":98,"target":16}, -{"source":98,"target":58}, -{"source":98,"target":62}, -{"source":99,"target":1}, -{"source":99,"target":25}, -{"source":99,"target":64}, -{"source":99,"target":151}, -{"source":101,"target":116}, -{"source":101,"target":171}, -{"source":101,"target":191}, -{"source":102,"target":24}, -{"source":102,"target":75}, -{"source":102,"target":172}, -{"source":104,"target":2}, -{"source":104,"target":26}, -{"source":104,"target":67}, -{"source":104,"target":81}, -{"source":104,"target":107}, -{"source":104,"target":124}, -{"source":104,"target":150}, -{"source":107,"target":2}, -{"source":107,"target":104}, -{"source":107,"target":150}, -{"source":109,"target":17}, -{"source":109,"target":66}, -{"source":109,"target":184}, -{"source":111,"target":140}, -{"source":111,"target":181}, -{"source":112,"target":58}, -{"source":113,"target":35}, -{"source":113,"target":141}, -{"source":114,"target":1}, -{"source":114,"target":21}, -{"source":114,"target":39}, -{"source":114,"target":151}, -{"source":115,"target":2}, -{"source":115,"target":162}, -{"source":116,"target":101}, -{"source":116,"target":159}, -{"source":116,"target":166}, -{"source":116,"target":171}, -{"source":116,"target":191}, -{"source":116,"target":192}, -{"source":117,"target":13}, -{"source":117,"target":35}, -{"source":117,"target":74}, -{"source":117,"target":90}, -{"source":117,"target":172}, -{"source":118,"target":4}, -{"source":118,"target":22}, -{"source":118,"target":159}, -{"source":118,"target":191}, -{"source":120,"target":35}, -{"source":120,"target":74}, -{"source":121,"target":16}, -{"source":121,"target":62}, -{"source":123,"target":38}, -{"source":123,"target":71}, -{"source":124,"target":2}, -{"source":124,"target":18}, -{"source":124,"target":26}, -{"source":124,"target":33}, -{"source":124,"target":95}, -{"source":124,"target":104}, -{"source":124,"target":125}, -{"source":125,"target":18}, -{"source":125,"target":29}, -{"source":125,"target":33}, -{"source":125,"target":124}, -{"source":126,"target":35}, -{"source":126,"target":141}, -{"source":126,"target":160}, -{"source":127,"target":57}, -{"source":127,"target":141}, -{"source":127,"target":167}, -{"source":128,"target":149}, -{"source":128,"target":182}, -{"source":128,"target":190}, -{"source":129,"target":0}, -{"source":129,"target":35}, -{"source":129,"target":74}, -{"source":129,"target":76}, -{"source":131,"target":36}, -{"source":131,"target":38}, -{"source":132,"target":75}, -{"source":133,"target":6}, -{"source":133,"target":20}, -{"source":133,"target":23}, -{"source":134,"target":20}, -{"source":134,"target":23}, -{"source":134,"target":34}, -{"source":134,"target":36}, -{"source":134,"target":49}, -{"source":136,"target":15}, -{"source":136,"target":42}, -{"source":136,"target":62}, -{"source":136,"target":97}, -{"source":136,"target":141}, -{"source":136,"target":155}, -{"source":136,"target":181}, -{"source":137,"target":162}, -{"source":138,"target":149}, -{"source":139,"target":4}, -{"source":139,"target":29}, -{"source":139,"target":32}, -{"source":139,"target":43}, -{"source":139,"target":59}, -{"source":140,"target":25}, -{"source":140,"target":72}, -{"source":140,"target":111}, -{"source":140,"target":151}, -{"source":140,"target":181}, -{"source":141,"target":10}, -{"source":141,"target":15}, -{"source":141,"target":35}, -{"source":141,"target":54}, -{"source":141,"target":57}, -{"source":141,"target":61}, -{"source":141,"target":85}, -{"source":141,"target":91}, -{"source":141,"target":97}, -{"source":141,"target":113}, -{"source":141,"target":126}, -{"source":141,"target":127}, -{"source":141,"target":136}, -{"source":141,"target":181}, -{"source":142,"target":27}, -{"source":142,"target":43}, -{"source":142,"target":171}, -{"source":142,"target":180}, -{"source":147,"target":80}, -{"source":149,"target":77}, -{"source":149,"target":84}, -{"source":149,"target":88}, -{"source":149,"target":128}, -{"source":149,"target":138}, -{"source":149,"target":182}, -{"source":149,"target":190}, -{"source":150,"target":60}, -{"source":150,"target":67}, -{"source":150,"target":68}, -{"source":150,"target":104}, -{"source":150,"target":107}, -{"source":151,"target":21}, -{"source":151,"target":25}, -{"source":151,"target":39}, -{"source":151,"target":72}, -{"source":151,"target":99}, -{"source":151,"target":114}, -{"source":151,"target":140}, -{"source":153,"target":67}, -{"source":153,"target":94}, -{"source":155,"target":9}, -{"source":155,"target":42}, -{"source":155,"target":72}, -{"source":155,"target":136}, -{"source":155,"target":181}, -{"source":156,"target":9}, -{"source":156,"target":39}, -{"source":156,"target":72}, -{"source":156,"target":80}, -{"source":158,"target":45}, -{"source":158,"target":55}, -{"source":158,"target":86}, -{"source":159,"target":22}, -{"source":159,"target":93}, -{"source":159,"target":116}, -{"source":159,"target":118}, -{"source":159,"target":166}, -{"source":159,"target":192}, -{"source":160,"target":126}, -{"source":161,"target":32}, -{"source":161,"target":43}, -{"source":161,"target":55}, -{"source":161,"target":86}, -{"source":161,"target":164}, -{"source":161,"target":180}, -{"source":162,"target":3}, -{"source":162,"target":58}, -{"source":162,"target":115}, -{"source":162,"target":137}, -{"source":164,"target":32}, -{"source":164,"target":33}, -{"source":164,"target":50}, -{"source":164,"target":53}, -{"source":164,"target":55}, -{"source":164,"target":95}, -{"source":164,"target":161}, -{"source":165,"target":23}, -{"source":165,"target":69}, -{"source":166,"target":116}, -{"source":166,"target":159}, -{"source":167,"target":57}, -{"source":167,"target":127}, -{"source":168,"target":9}, -{"source":168,"target":58}, -{"source":168,"target":62}, -{"source":168,"target":80}, -{"source":168,"target":96}, -{"source":169,"target":77}, -{"source":169,"target":79}, -{"source":169,"target":84}, -{"source":169,"target":92}, -{"source":169,"target":177}, -{"source":170,"target":0}, -{"source":170,"target":35}, -{"source":170,"target":89}, -{"source":170,"target":186}, -{"source":171,"target":27}, -{"source":171,"target":43}, -{"source":171,"target":86}, -{"source":171,"target":101}, -{"source":171,"target":116}, -{"source":171,"target":142}, -{"source":171,"target":180}, -{"source":171,"target":191}, -{"source":172,"target":28}, -{"source":172,"target":90}, -{"source":172,"target":102}, -{"source":172,"target":117}, -{"source":173,"target":18}, -{"source":173,"target":26}, -{"source":173,"target":63}, -{"source":176,"target":2}, -{"source":176,"target":95}, -{"source":177,"target":7}, -{"source":177,"target":10}, -{"source":177,"target":25}, -{"source":177,"target":61}, -{"source":177,"target":64}, -{"source":177,"target":76}, -{"source":177,"target":77}, -{"source":177,"target":169}, -{"source":178,"target":0}, -{"source":178,"target":76}, -{"source":178,"target":85}, -{"source":178,"target":186}, -{"source":180,"target":43}, -{"source":180,"target":86}, -{"source":180,"target":142}, -{"source":180,"target":161}, -{"source":180,"target":171}, -{"source":181,"target":15}, -{"source":181,"target":72}, -{"source":181,"target":111}, -{"source":181,"target":136}, -{"source":181,"target":140}, -{"source":181,"target":141}, -{"source":181,"target":155}, -{"source":182,"target":128}, -{"source":182,"target":149}, -{"source":183,"target":78}, -{"source":184,"target":30}, -{"source":184,"target":109}, -{"source":185,"target":6}, -{"source":185,"target":23}, -{"source":186,"target":0}, -{"source":186,"target":85}, -{"source":186,"target":89}, -{"source":186,"target":170}, -{"source":186,"target":178}, -{"source":188,"target":23}, -{"source":188,"target":36}, -{"source":188,"target":69}, -{"source":189,"target":28}, -{"source":189,"target":35}, -{"source":189,"target":90}, -{"source":190,"target":128}, -{"source":190,"target":149}, -{"source":191,"target":4}, -{"source":191,"target":22}, -{"source":191,"target":43}, -{"source":191,"target":101}, -{"source":191,"target":116}, -{"source":191,"target":118}, -{"source":191,"target":171}, -{"source":191,"target":192}, -{"source":192,"target":22}, -{"source":192,"target":116}, -{"source":192,"target":159}, -{"source":192,"target":191}] \ No newline at end of file +world_borders = [ + {"source": 0, "target": 35}, + {"source": 0, "target": 76}, + {"source": 0, "target": 129}, + {"source": 0, "target": 170}, + {"source": 0, "target": 178}, + {"source": 0, "target": 186}, + {"source": 1, "target": 64}, + {"source": 1, "target": 99}, + {"source": 1, "target": 114}, + {"source": 2, "target": 95}, + {"source": 2, "target": 104}, + {"source": 2, "target": 107}, + {"source": 2, "target": 115}, + {"source": 2, "target": 124}, + {"source": 2, "target": 176}, + {"source": 3, "target": 58}, + {"source": 3, "target": 162}, + {"source": 4, "target": 43}, + {"source": 4, "target": 118}, + {"source": 4, "target": 139}, + {"source": 4, "target": 191}, + {"source": 6, "target": 20}, + {"source": 6, "target": 23}, + {"source": 6, "target": 34}, + {"source": 6, "target": 133}, + {"source": 6, "target": 185}, + {"source": 7, "target": 10}, + {"source": 7, "target": 61}, + {"source": 7, "target": 76}, + {"source": 7, "target": 177}, + {"source": 9, "target": 42}, + {"source": 9, "target": 62}, + {"source": 9, "target": 72}, + {"source": 9, "target": 80}, + {"source": 9, "target": 96}, + {"source": 9, "target": 155}, + {"source": 9, "target": 156}, + {"source": 9, "target": 168}, + {"source": 10, "target": 7}, + {"source": 10, "target": 61}, + {"source": 10, "target": 76}, + {"source": 10, "target": 141}, + {"source": 10, "target": 177}, + {"source": 13, "target": 74}, + {"source": 13, "target": 117}, + {"source": 15, "target": 91}, + {"source": 15, "target": 97}, + {"source": 15, "target": 136}, + {"source": 15, "target": 141}, + {"source": 15, "target": 181}, + {"source": 16, "target": 58}, + {"source": 16, "target": 62}, + {"source": 16, "target": 98}, + {"source": 16, "target": 121}, + {"source": 17, "target": 66}, + {"source": 17, "target": 109}, + {"source": 18, "target": 26}, + {"source": 18, "target": 124}, + {"source": 18, "target": 125}, + {"source": 18, "target": 173}, + {"source": 19, "target": 35}, + {"source": 19, "target": 74}, + {"source": 20, "target": 6}, + {"source": 20, "target": 23}, + {"source": 20, "target": 34}, + {"source": 20, "target": 133}, + {"source": 20, "target": 134}, + {"source": 21, "target": 39}, + {"source": 21, "target": 114}, + {"source": 21, "target": 151}, + {"source": 22, "target": 118}, + {"source": 22, "target": 159}, + {"source": 22, "target": 191}, + {"source": 22, "target": 192}, + {"source": 23, "target": 6}, + {"source": 23, "target": 20}, + {"source": 23, "target": 36}, + {"source": 23, "target": 69}, + {"source": 23, "target": 133}, + {"source": 23, "target": 134}, + {"source": 23, "target": 165}, + {"source": 23, "target": 185}, + {"source": 23, "target": 188}, + {"source": 24, "target": 102}, + {"source": 25, "target": 64}, + {"source": 25, "target": 99}, + {"source": 25, "target": 140}, + {"source": 25, "target": 151}, + {"source": 25, "target": 177}, + {"source": 26, "target": 18}, + {"source": 26, "target": 63}, + {"source": 26, "target": 81}, + {"source": 26, "target": 104}, + {"source": 26, "target": 124}, + {"source": 26, "target": 173}, + {"source": 27, "target": 43}, + {"source": 27, "target": 142}, + {"source": 27, "target": 171}, + {"source": 28, "target": 90}, + {"source": 28, "target": 172}, + {"source": 28, "target": 189}, + {"source": 29, "target": 32}, + {"source": 29, "target": 33}, + {"source": 29, "target": 52}, + {"source": 29, "target": 59}, + {"source": 29, "target": 125}, + {"source": 29, "target": 139}, + {"source": 30, "target": 184}, + {"source": 32, "target": 29}, + {"source": 32, "target": 33}, + {"source": 32, "target": 43}, + {"source": 32, "target": 139}, + {"source": 32, "target": 161}, + {"source": 32, "target": 164}, + {"source": 33, "target": 29}, + {"source": 33, "target": 32}, + {"source": 33, "target": 95}, + {"source": 33, "target": 124}, + {"source": 33, "target": 125}, + {"source": 33, "target": 164}, + {"source": 34, "target": 6}, + {"source": 34, "target": 20}, + {"source": 34, "target": 134}, + {"source": 35, "target": 0}, + {"source": 35, "target": 19}, + {"source": 35, "target": 74}, + {"source": 35, "target": 85}, + {"source": 35, "target": 89}, + {"source": 35, "target": 90}, + {"source": 35, "target": 113}, + {"source": 35, "target": 117}, + {"source": 35, "target": 120}, + {"source": 35, "target": 126}, + {"source": 35, "target": 129}, + {"source": 35, "target": 141}, + {"source": 35, "target": 170}, + {"source": 35, "target": 189}, + {"source": 36, "target": 23}, + {"source": 36, "target": 49}, + {"source": 36, "target": 131}, + {"source": 36, "target": 134}, + {"source": 36, "target": 188}, + {"source": 38, "target": 123}, + {"source": 38, "target": 131}, + {"source": 39, "target": 21}, + {"source": 39, "target": 72}, + {"source": 39, "target": 114}, + {"source": 39, "target": 151}, + {"source": 39, "target": 156}, + {"source": 42, "target": 9}, + {"source": 42, "target": 62}, + {"source": 42, "target": 136}, + {"source": 42, "target": 155}, + {"source": 43, "target": 4}, + {"source": 43, "target": 27}, + {"source": 43, "target": 32}, + {"source": 43, "target": 139}, + {"source": 43, "target": 142}, + {"source": 43, "target": 161}, + {"source": 43, "target": 171}, + {"source": 43, "target": 180}, + {"source": 43, "target": 191}, + {"source": 44, "target": 62}, + {"source": 45, "target": 53}, + {"source": 45, "target": 55}, + {"source": 45, "target": 158}, + {"source": 47, "target": 70}, + {"source": 48, "target": 75}, + {"source": 49, "target": 36}, + {"source": 49, "target": 134}, + {"source": 50, "target": 79}, + {"source": 50, "target": 95}, + {"source": 50, "target": 164}, + {"source": 51, "target": 66}, + {"source": 51, "target": 71}, + {"source": 52, "target": 29}, + {"source": 52, "target": 59}, + {"source": 53, "target": 45}, + {"source": 53, "target": 55}, + {"source": 53, "target": 164}, + {"source": 54, "target": 91}, + {"source": 54, "target": 141}, + {"source": 55, "target": 45}, + {"source": 55, "target": 53}, + {"source": 55, "target": 86}, + {"source": 55, "target": 158}, + {"source": 55, "target": 161}, + {"source": 55, "target": 164}, + {"source": 57, "target": 127}, + {"source": 57, "target": 141}, + {"source": 57, "target": 167}, + {"source": 58, "target": 3}, + {"source": 58, "target": 16}, + {"source": 58, "target": 62}, + {"source": 58, "target": 80}, + {"source": 58, "target": 98}, + {"source": 58, "target": 112}, + {"source": 58, "target": 162}, + {"source": 58, "target": 168}, + {"source": 59, "target": 29}, + {"source": 59, "target": 52}, + {"source": 59, "target": 139}, + {"source": 60, "target": 150}, + {"source": 61, "target": 7}, + {"source": 61, "target": 10}, + {"source": 61, "target": 141}, + {"source": 61, "target": 177}, + {"source": 62, "target": 9}, + {"source": 62, "target": 16}, + {"source": 62, "target": 42}, + {"source": 62, "target": 44}, + {"source": 62, "target": 58}, + {"source": 62, "target": 98}, + {"source": 62, "target": 121}, + {"source": 62, "target": 136}, + {"source": 62, "target": 168}, + {"source": 63, "target": 26}, + {"source": 63, "target": 81}, + {"source": 63, "target": 173}, + {"source": 64, "target": 1}, + {"source": 64, "target": 25}, + {"source": 64, "target": 99}, + {"source": 64, "target": 177}, + {"source": 66, "target": 17}, + {"source": 66, "target": 51}, + {"source": 66, "target": 71}, + {"source": 66, "target": 109}, + {"source": 67, "target": 68}, + {"source": 67, "target": 81}, + {"source": 67, "target": 94}, + {"source": 67, "target": 104}, + {"source": 67, "target": 150}, + {"source": 67, "target": 153}, + {"source": 68, "target": 67}, + {"source": 68, "target": 150}, + {"source": 69, "target": 23}, + {"source": 69, "target": 165}, + {"source": 69, "target": 188}, + {"source": 70, "target": 47}, + {"source": 71, "target": 51}, + {"source": 71, "target": 66}, + {"source": 71, "target": 123}, + {"source": 72, "target": 9}, + {"source": 72, "target": 39}, + {"source": 72, "target": 140}, + {"source": 72, "target": 151}, + {"source": 72, "target": 155}, + {"source": 72, "target": 156}, + {"source": 72, "target": 181}, + {"source": 74, "target": 13}, + {"source": 74, "target": 19}, + {"source": 74, "target": 35}, + {"source": 74, "target": 117}, + {"source": 74, "target": 120}, + {"source": 74, "target": 129}, + {"source": 75, "target": 48}, + {"source": 75, "target": 102}, + {"source": 75, "target": 132}, + {"source": 76, "target": 0}, + {"source": 76, "target": 7}, + {"source": 76, "target": 10}, + {"source": 76, "target": 77}, + {"source": 76, "target": 129}, + {"source": 76, "target": 177}, + {"source": 76, "target": 178}, + {"source": 77, "target": 76}, + {"source": 77, "target": 84}, + {"source": 77, "target": 88}, + {"source": 77, "target": 149}, + {"source": 77, "target": 169}, + {"source": 77, "target": 177}, + {"source": 78, "target": 183}, + {"source": 79, "target": 50}, + {"source": 79, "target": 84}, + {"source": 79, "target": 92}, + {"source": 79, "target": 169}, + {"source": 80, "target": 9}, + {"source": 80, "target": 58}, + {"source": 80, "target": 147}, + {"source": 80, "target": 156}, + {"source": 80, "target": 168}, + {"source": 81, "target": 26}, + {"source": 81, "target": 63}, + {"source": 81, "target": 67}, + {"source": 81, "target": 94}, + {"source": 81, "target": 104}, + {"source": 84, "target": 77}, + {"source": 84, "target": 79}, + {"source": 84, "target": 149}, + {"source": 84, "target": 169}, + {"source": 85, "target": 35}, + {"source": 85, "target": 89}, + {"source": 85, "target": 141}, + {"source": 85, "target": 178}, + {"source": 85, "target": 186}, + {"source": 86, "target": 55}, + {"source": 86, "target": 158}, + {"source": 86, "target": 161}, + {"source": 86, "target": 171}, + {"source": 86, "target": 180}, + {"source": 88, "target": 77}, + {"source": 88, "target": 149}, + {"source": 89, "target": 35}, + {"source": 89, "target": 85}, + {"source": 89, "target": 170}, + {"source": 89, "target": 186}, + {"source": 90, "target": 28}, + {"source": 90, "target": 35}, + {"source": 90, "target": 117}, + {"source": 90, "target": 172}, + {"source": 90, "target": 189}, + {"source": 91, "target": 15}, + {"source": 91, "target": 54}, + {"source": 91, "target": 97}, + {"source": 91, "target": 141}, + {"source": 92, "target": 79}, + {"source": 92, "target": 169}, + {"source": 93, "target": 159}, + {"source": 94, "target": 67}, + {"source": 94, "target": 81}, + {"source": 94, "target": 153}, + {"source": 95, "target": 2}, + {"source": 95, "target": 33}, + {"source": 95, "target": 50}, + {"source": 95, "target": 124}, + {"source": 95, "target": 164}, + {"source": 95, "target": 176}, + {"source": 96, "target": 9}, + {"source": 96, "target": 168}, + {"source": 97, "target": 15}, + {"source": 97, "target": 91}, + {"source": 97, "target": 136}, + {"source": 97, "target": 141}, + {"source": 98, "target": 16}, + {"source": 98, "target": 58}, + {"source": 98, "target": 62}, + {"source": 99, "target": 1}, + {"source": 99, "target": 25}, + {"source": 99, "target": 64}, + {"source": 99, "target": 151}, + {"source": 101, "target": 116}, + {"source": 101, "target": 171}, + {"source": 101, "target": 191}, + {"source": 102, "target": 24}, + {"source": 102, "target": 75}, + {"source": 102, "target": 172}, + {"source": 104, "target": 2}, + {"source": 104, "target": 26}, + {"source": 104, "target": 67}, + {"source": 104, "target": 81}, + {"source": 104, "target": 107}, + {"source": 104, "target": 124}, + {"source": 104, "target": 150}, + {"source": 107, "target": 2}, + {"source": 107, "target": 104}, + {"source": 107, "target": 150}, + {"source": 109, "target": 17}, + {"source": 109, "target": 66}, + {"source": 109, "target": 184}, + {"source": 111, "target": 140}, + {"source": 111, "target": 181}, + {"source": 112, "target": 58}, + {"source": 113, "target": 35}, + {"source": 113, "target": 141}, + {"source": 114, "target": 1}, + {"source": 114, "target": 21}, + {"source": 114, "target": 39}, + {"source": 114, "target": 151}, + {"source": 115, "target": 2}, + {"source": 115, "target": 162}, + {"source": 116, "target": 101}, + {"source": 116, "target": 159}, + {"source": 116, "target": 166}, + {"source": 116, "target": 171}, + {"source": 116, "target": 191}, + {"source": 116, "target": 192}, + {"source": 117, "target": 13}, + {"source": 117, "target": 35}, + {"source": 117, "target": 74}, + {"source": 117, "target": 90}, + {"source": 117, "target": 172}, + {"source": 118, "target": 4}, + {"source": 118, "target": 22}, + {"source": 118, "target": 159}, + {"source": 118, "target": 191}, + {"source": 120, "target": 35}, + {"source": 120, "target": 74}, + {"source": 121, "target": 16}, + {"source": 121, "target": 62}, + {"source": 123, "target": 38}, + {"source": 123, "target": 71}, + {"source": 124, "target": 2}, + {"source": 124, "target": 18}, + {"source": 124, "target": 26}, + {"source": 124, "target": 33}, + {"source": 124, "target": 95}, + {"source": 124, "target": 104}, + {"source": 124, "target": 125}, + {"source": 125, "target": 18}, + {"source": 125, "target": 29}, + {"source": 125, "target": 33}, + {"source": 125, "target": 124}, + {"source": 126, "target": 35}, + {"source": 126, "target": 141}, + {"source": 126, "target": 160}, + {"source": 127, "target": 57}, + {"source": 127, "target": 141}, + {"source": 127, "target": 167}, + {"source": 128, "target": 149}, + {"source": 128, "target": 182}, + {"source": 128, "target": 190}, + {"source": 129, "target": 0}, + {"source": 129, "target": 35}, + {"source": 129, "target": 74}, + {"source": 129, "target": 76}, + {"source": 131, "target": 36}, + {"source": 131, "target": 38}, + {"source": 132, "target": 75}, + {"source": 133, "target": 6}, + {"source": 133, "target": 20}, + {"source": 133, "target": 23}, + {"source": 134, "target": 20}, + {"source": 134, "target": 23}, + {"source": 134, "target": 34}, + {"source": 134, "target": 36}, + {"source": 134, "target": 49}, + {"source": 136, "target": 15}, + {"source": 136, "target": 42}, + {"source": 136, "target": 62}, + {"source": 136, "target": 97}, + {"source": 136, "target": 141}, + {"source": 136, "target": 155}, + {"source": 136, "target": 181}, + {"source": 137, "target": 162}, + {"source": 138, "target": 149}, + {"source": 139, "target": 4}, + {"source": 139, "target": 29}, + {"source": 139, "target": 32}, + {"source": 139, "target": 43}, + {"source": 139, "target": 59}, + {"source": 140, "target": 25}, + {"source": 140, "target": 72}, + {"source": 140, "target": 111}, + {"source": 140, "target": 151}, + {"source": 140, "target": 181}, + {"source": 141, "target": 10}, + {"source": 141, "target": 15}, + {"source": 141, "target": 35}, + {"source": 141, "target": 54}, + {"source": 141, "target": 57}, + {"source": 141, "target": 61}, + {"source": 141, "target": 85}, + {"source": 141, "target": 91}, + {"source": 141, "target": 97}, + {"source": 141, "target": 113}, + {"source": 141, "target": 126}, + {"source": 141, "target": 127}, + {"source": 141, "target": 136}, + {"source": 141, "target": 181}, + {"source": 142, "target": 27}, + {"source": 142, "target": 43}, + {"source": 142, "target": 171}, + {"source": 142, "target": 180}, + {"source": 147, "target": 80}, + {"source": 149, "target": 77}, + {"source": 149, "target": 84}, + {"source": 149, "target": 88}, + {"source": 149, "target": 128}, + {"source": 149, "target": 138}, + {"source": 149, "target": 182}, + {"source": 149, "target": 190}, + {"source": 150, "target": 60}, + {"source": 150, "target": 67}, + {"source": 150, "target": 68}, + {"source": 150, "target": 104}, + {"source": 150, "target": 107}, + {"source": 151, "target": 21}, + {"source": 151, "target": 25}, + {"source": 151, "target": 39}, + {"source": 151, "target": 72}, + {"source": 151, "target": 99}, + {"source": 151, "target": 114}, + {"source": 151, "target": 140}, + {"source": 153, "target": 67}, + {"source": 153, "target": 94}, + {"source": 155, "target": 9}, + {"source": 155, "target": 42}, + {"source": 155, "target": 72}, + {"source": 155, "target": 136}, + {"source": 155, "target": 181}, + {"source": 156, "target": 9}, + {"source": 156, "target": 39}, + {"source": 156, "target": 72}, + {"source": 156, "target": 80}, + {"source": 158, "target": 45}, + {"source": 158, "target": 55}, + {"source": 158, "target": 86}, + {"source": 159, "target": 22}, + {"source": 159, "target": 93}, + {"source": 159, "target": 116}, + {"source": 159, "target": 118}, + {"source": 159, "target": 166}, + {"source": 159, "target": 192}, + {"source": 160, "target": 126}, + {"source": 161, "target": 32}, + {"source": 161, "target": 43}, + {"source": 161, "target": 55}, + {"source": 161, "target": 86}, + {"source": 161, "target": 164}, + {"source": 161, "target": 180}, + {"source": 162, "target": 3}, + {"source": 162, "target": 58}, + {"source": 162, "target": 115}, + {"source": 162, "target": 137}, + {"source": 164, "target": 32}, + {"source": 164, "target": 33}, + {"source": 164, "target": 50}, + {"source": 164, "target": 53}, + {"source": 164, "target": 55}, + {"source": 164, "target": 95}, + {"source": 164, "target": 161}, + {"source": 165, "target": 23}, + {"source": 165, "target": 69}, + {"source": 166, "target": 116}, + {"source": 166, "target": 159}, + {"source": 167, "target": 57}, + {"source": 167, "target": 127}, + {"source": 168, "target": 9}, + {"source": 168, "target": 58}, + {"source": 168, "target": 62}, + {"source": 168, "target": 80}, + {"source": 168, "target": 96}, + {"source": 169, "target": 77}, + {"source": 169, "target": 79}, + {"source": 169, "target": 84}, + {"source": 169, "target": 92}, + {"source": 169, "target": 177}, + {"source": 170, "target": 0}, + {"source": 170, "target": 35}, + {"source": 170, "target": 89}, + {"source": 170, "target": 186}, + {"source": 171, "target": 27}, + {"source": 171, "target": 43}, + {"source": 171, "target": 86}, + {"source": 171, "target": 101}, + {"source": 171, "target": 116}, + {"source": 171, "target": 142}, + {"source": 171, "target": 180}, + {"source": 171, "target": 191}, + {"source": 172, "target": 28}, + {"source": 172, "target": 90}, + {"source": 172, "target": 102}, + {"source": 172, "target": 117}, + {"source": 173, "target": 18}, + {"source": 173, "target": 26}, + {"source": 173, "target": 63}, + {"source": 176, "target": 2}, + {"source": 176, "target": 95}, + {"source": 177, "target": 7}, + {"source": 177, "target": 10}, + {"source": 177, "target": 25}, + {"source": 177, "target": 61}, + {"source": 177, "target": 64}, + {"source": 177, "target": 76}, + {"source": 177, "target": 77}, + {"source": 177, "target": 169}, + {"source": 178, "target": 0}, + {"source": 178, "target": 76}, + {"source": 178, "target": 85}, + {"source": 178, "target": 186}, + {"source": 180, "target": 43}, + {"source": 180, "target": 86}, + {"source": 180, "target": 142}, + {"source": 180, "target": 161}, + {"source": 180, "target": 171}, + {"source": 181, "target": 15}, + {"source": 181, "target": 72}, + {"source": 181, "target": 111}, + {"source": 181, "target": 136}, + {"source": 181, "target": 140}, + {"source": 181, "target": 141}, + {"source": 181, "target": 155}, + {"source": 182, "target": 128}, + {"source": 182, "target": 149}, + {"source": 183, "target": 78}, + {"source": 184, "target": 30}, + {"source": 184, "target": 109}, + {"source": 185, "target": 6}, + {"source": 185, "target": 23}, + {"source": 186, "target": 0}, + {"source": 186, "target": 85}, + {"source": 186, "target": 89}, + {"source": 186, "target": 170}, + {"source": 186, "target": 178}, + {"source": 188, "target": 23}, + {"source": 188, "target": 36}, + {"source": 188, "target": 69}, + {"source": 189, "target": 28}, + {"source": 189, "target": 35}, + {"source": 189, "target": 90}, + {"source": 190, "target": 128}, + {"source": 190, "target": 149}, + {"source": 191, "target": 4}, + {"source": 191, "target": 22}, + {"source": 191, "target": 43}, + {"source": 191, "target": 101}, + {"source": 191, "target": 116}, + {"source": 191, "target": 118}, + {"source": 191, "target": 171}, + {"source": 191, "target": 192}, + {"source": 192, "target": 22}, + {"source": 192, "target": 116}, + {"source": 192, "target": 159}, + {"source": 192, "target": 191}, +] From b0ef99d27c2b513f3c4001f8140c0f293638540b Mon Sep 17 00:00:00 2001 From: KOBAYASHI Takeshi Date: Mon, 13 Jun 2022 18:31:04 +0900 Subject: [PATCH 02/19] revise get spike data --- spinnaker_csp/snn_creator.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spinnaker_csp/snn_creator.py b/spinnaker_csp/snn_creator.py index 95e5404..9723328 100644 --- a/spinnaker_csp/snn_creator.py +++ b/spinnaker_csp/snn_creator.py @@ -677,7 +677,9 @@ def save(self, filename, DAT=False): ] spikes = [] for population in self.var_pops: - spikes.append(population.get_data("spikes")) + spikes.append( + population.get_data("spikes").segments[0].spiketrains[0].magnitude + ) np.savez(filepath, params=params, *spikes) self.spikes_file = filename From c46025f9d59fa5f2e1d362588607cd249262eccd Mon Sep 17 00:00:00 2001 From: KOBAYASHI Takeshi Date: Mon, 13 Jun 2022 19:11:24 +0900 Subject: [PATCH 03/19] revise get spikes --- spinnaker_csp/snn_creator.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/spinnaker_csp/snn_creator.py b/spinnaker_csp/snn_creator.py index 9723328..69f0c11 100644 --- a/spinnaker_csp/snn_creator.py +++ b/spinnaker_csp/snn_creator.py @@ -706,10 +706,13 @@ def save_stimulation(self, filename, DAT=False): % (pulse_index, filename, var_index) ) filepath = "results/%s_stim_spikes_binary" % filename + spikes = [] for pulse in self.stim_pops: for population in pulse: - spikes = population.getSpikes() - np.save(filepath, spikes) + spikes.append( + population.get_data("spikes").segments[0].spiketrains[0].magnitude + ) + np.savez(filepath, *spikes) def save_dissipation(self, filename, DAT=False): """Save spikes recorded from depressing noise sources. @@ -734,10 +737,13 @@ def save_dissipation(self, filename, DAT=False): % (pulse_index, filename, var_index) ) filepath = "results/%s_diss_spikes_binary" % filename + spikes = [] for pulse in self.diss_pops: for population in pulse: - spikes = population.getSpikes() - np.save(filepath, spikes) + spikes.append( + population.get_data("spikes").segments[0].spiketrains[0].magnitude + ) + np.savez(filepath, *spikes) def report_network_params(self, filename=False): """Report the network dimensions and parameters to standard output or file. @@ -770,7 +776,9 @@ def projections_counter(projections_pop): synapse_number: the number of synapses generated by the list of projections. """ # synapse_number = sum(map(lambda x: len(x.getWeights('list').flatten()), projections_pop)) - synapse_number = lambda x: x.get(["weight"], "list").flatten() + synapse_number = lambda x: x.get( + ["weight"], "list", with_address=False + ).flatten() synapse_number = map(synapse_number, projections_pop) synapse_number = map(len, synapse_number) synapse_number = sum(synapse_number) From b7105813216be30abc6acfcd54d3e1749fa77fd8 Mon Sep 17 00:00:00 2001 From: KOBAYASHI Takeshi Date: Mon, 13 Jun 2022 20:40:36 +0900 Subject: [PATCH 04/19] revise spike get --- spinnaker_csp/snn_creator.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spinnaker_csp/snn_creator.py b/spinnaker_csp/snn_creator.py index 69f0c11..6b8e718 100644 --- a/spinnaker_csp/snn_creator.py +++ b/spinnaker_csp/snn_creator.py @@ -678,7 +678,7 @@ def save(self, filename, DAT=False): spikes = [] for population in self.var_pops: spikes.append( - population.get_data("spikes").segments[0].spiketrains[0].magnitude + [i, spike[i].magnitude for i, spike in enumerate(population.get_data("spikes").segments[0].spiketrains)] ) np.savez(filepath, params=params, *spikes) self.spikes_file = filename @@ -710,7 +710,7 @@ def save_stimulation(self, filename, DAT=False): for pulse in self.stim_pops: for population in pulse: spikes.append( - population.get_data("spikes").segments[0].spiketrains[0].magnitude + [i, spike[i].magnitude for i, spike in enumerate(population.get_data("spikes").segments[0].spiketrains)] ) np.savez(filepath, *spikes) @@ -741,7 +741,7 @@ def save_dissipation(self, filename, DAT=False): for pulse in self.diss_pops: for population in pulse: spikes.append( - population.get_data("spikes").segments[0].spiketrains[0].magnitude + [i, spike[i].magnitude for i, spike in enumerate(population.get_data("spikes").segments[0].spiketrains)] ) np.savez(filepath, *spikes) From 7d35b3e48d061d23af392d630d2b8f1ab45dc0cd Mon Sep 17 00:00:00 2001 From: KOBAYASHI Takeshi Date: Mon, 13 Jun 2022 20:45:37 +0900 Subject: [PATCH 05/19] revise spike get --- spinnaker_csp/snn_creator.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/spinnaker_csp/snn_creator.py b/spinnaker_csp/snn_creator.py index 6b8e718..7d0b679 100644 --- a/spinnaker_csp/snn_creator.py +++ b/spinnaker_csp/snn_creator.py @@ -678,7 +678,12 @@ def save(self, filename, DAT=False): spikes = [] for population in self.var_pops: spikes.append( - [i, spike[i].magnitude for i, spike in enumerate(population.get_data("spikes").segments[0].spiketrains)] + [ + [i, spike.magnitude] + for i, spike in enumerate( + population.get_data("spikes").segments[0].spiketrains + ) + ] ) np.savez(filepath, params=params, *spikes) self.spikes_file = filename @@ -710,7 +715,12 @@ def save_stimulation(self, filename, DAT=False): for pulse in self.stim_pops: for population in pulse: spikes.append( - [i, spike[i].magnitude for i, spike in enumerate(population.get_data("spikes").segments[0].spiketrains)] + [ + [i, spike.magnitude] + for i, spike in enumerate( + population.get_data("spikes").segments[0].spiketrains + ) + ] ) np.savez(filepath, *spikes) @@ -741,7 +751,12 @@ def save_dissipation(self, filename, DAT=False): for pulse in self.diss_pops: for population in pulse: spikes.append( - [i, spike[i].magnitude for i, spike in enumerate(population.get_data("spikes").segments[0].spiketrains)] + [ + [i, spike.magnitude] + for i, spike in enumerate( + population.get_data("spikes").segments[0].spiketrains + ) + ] ) np.savez(filepath, *spikes) From ab0b963d9a0d73851173bb4d497eb00e90b4fcc1 Mon Sep 17 00:00:00 2001 From: KOBAYASHI Takeshi Date: Mon, 13 Jun 2022 20:47:08 +0900 Subject: [PATCH 06/19] revise import for load_data --- spinnaker_csp/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spinnaker_csp/__init__.py b/spinnaker_csp/__init__.py index f807989..bbb8c9c 100644 --- a/spinnaker_csp/__init__.py +++ b/spinnaker_csp/__init__.py @@ -38,7 +38,7 @@ """ from .snn_creator import CSP -from .analysis import plot_entropy +from .analysis import plot_entropy, load_data from .translators.sudoku2csp import sudoku2csp from .translators.spin2csp import spin2csp from .translators.world_bordering_countries import world_borders, world_countries From bd64c5b040c6792e271c962b1d63c77991c4b56e Mon Sep 17 00:00:00 2001 From: KOBAYASHI Takeshi Date: Mon, 13 Jun 2022 21:00:26 +0900 Subject: [PATCH 07/19] revise get spike data --- spinnaker_csp/snn_creator.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/spinnaker_csp/snn_creator.py b/spinnaker_csp/snn_creator.py index 7d0b679..72092f7 100644 --- a/spinnaker_csp/snn_creator.py +++ b/spinnaker_csp/snn_creator.py @@ -679,7 +679,9 @@ def save(self, filename, DAT=False): for population in self.var_pops: spikes.append( [ - [i, spike.magnitude] + np.stack( + [i * np.ones_like(spike.magnitude), spike.magnitude] + ).transpose() for i, spike in enumerate( population.get_data("spikes").segments[0].spiketrains ) @@ -716,7 +718,9 @@ def save_stimulation(self, filename, DAT=False): for population in pulse: spikes.append( [ - [i, spike.magnitude] + np.stack( + [i * np.ones_like(spike.magnitude), spike.magnitude] + ).transpose() for i, spike in enumerate( population.get_data("spikes").segments[0].spiketrains ) @@ -752,7 +756,9 @@ def save_dissipation(self, filename, DAT=False): for population in pulse: spikes.append( [ - [i, spike.magnitude] + np.stack( + [i * np.ones_like(spike.magnitude), spike.magnitude] + ).transpose() for i, spike in enumerate( population.get_data("spikes").segments[0].spiketrains ) From 1f44d26b161cf40c36264714aabc5c8eae497c1a Mon Sep 17 00:00:00 2001 From: KOBAYASHI Takeshi Date: Mon, 13 Jun 2022 22:05:38 +0900 Subject: [PATCH 08/19] revise get spike data --- spinnaker_csp/snn_creator.py | 60 +++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/spinnaker_csp/snn_creator.py b/spinnaker_csp/snn_creator.py index 72092f7..f71d9aa 100644 --- a/spinnaker_csp/snn_creator.py +++ b/spinnaker_csp/snn_creator.py @@ -678,14 +678,17 @@ def save(self, filename, DAT=False): spikes = [] for population in self.var_pops: spikes.append( - [ - np.stack( - [i * np.ones_like(spike.magnitude), spike.magnitude] - ).transpose() - for i, spike in enumerate( - population.get_data("spikes").segments[0].spiketrains - ) - ] + np.concatenate( + [ + np.stack( + [i * np.ones_like(spike.magnitude), spike.magnitude] + ).transpose() + for i, spike in enumerate( + population.get_data("spikes").segments[0].spiketrains + ) + ], + axis=0, + ) ) np.savez(filepath, params=params, *spikes) self.spikes_file = filename @@ -717,14 +720,17 @@ def save_stimulation(self, filename, DAT=False): for pulse in self.stim_pops: for population in pulse: spikes.append( - [ - np.stack( - [i * np.ones_like(spike.magnitude), spike.magnitude] - ).transpose() - for i, spike in enumerate( - population.get_data("spikes").segments[0].spiketrains - ) - ] + np.concatenate( + [ + np.stack( + [i * np.ones_like(spike.magnitude), spike.magnitude] + ).transpose() + for i, spike in enumerate( + population.get_data("spikes").segments[0].spiketrains + ) + ], + axis=0, + ) ) np.savez(filepath, *spikes) @@ -755,14 +761,17 @@ def save_dissipation(self, filename, DAT=False): for pulse in self.diss_pops: for population in pulse: spikes.append( - [ - np.stack( - [i * np.ones_like(spike.magnitude), spike.magnitude] - ).transpose() - for i, spike in enumerate( - population.get_data("spikes").segments[0].spiketrains - ) - ] + np.concatenate( + [ + np.stack( + [i * np.ones_like(spike.magnitude), spike.magnitude] + ).transpose() + for i, spike in enumerate( + population.get_data("spikes").segments[0].spiketrains + ) + ], + axis=0, + ) ) np.savez(filepath, *spikes) @@ -797,8 +806,9 @@ def projections_counter(projections_pop): synapse_number: the number of synapses generated by the list of projections. """ # synapse_number = sum(map(lambda x: len(x.getWeights('list').flatten()), projections_pop)) + synapse_number = lambda x: x.get( - ["weight"], "list", with_address=False + "weight", "list", with_address=False ).flatten() synapse_number = map(synapse_number, projections_pop) synapse_number = map(len, synapse_number) From 45b7b35ae2f8b8590332490749ed1a34541ed8c6 Mon Sep 17 00:00:00 2001 From: KOBAYASHI Takeshi Date: Wed, 15 Jun 2022 18:56:32 +0900 Subject: [PATCH 09/19] revise synapse num --- spinnaker_csp/snn_creator.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/spinnaker_csp/snn_creator.py b/spinnaker_csp/snn_creator.py index f71d9aa..f630715 100644 --- a/spinnaker_csp/snn_creator.py +++ b/spinnaker_csp/snn_creator.py @@ -807,10 +807,13 @@ def projections_counter(projections_pop): """ # synapse_number = sum(map(lambda x: len(x.getWeights('list').flatten()), projections_pop)) - synapse_number = lambda x: x.get( + # synapse_number = lambda x: x.get( + # "weight", "list", with_address=False + # ).flatten() + get_synapse_number = lambda x: x.get( "weight", "list", with_address=False ).flatten() - synapse_number = map(synapse_number, projections_pop) + synapse_number = map(get_synapse_number, projections_pop) synapse_number = map(len, synapse_number) synapse_number = sum(synapse_number) return synapse_number From ac82a8bc8bd5b3be998d1ca4dd51982e374ff3af Mon Sep 17 00:00:00 2001 From: KOBAYASHI Takeshi Date: Thu, 16 Jun 2022 13:31:49 +0900 Subject: [PATCH 10/19] revise get synapse num --- spinnaker_csp/snn_creator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spinnaker_csp/snn_creator.py b/spinnaker_csp/snn_creator.py index f630715..25a6367 100644 --- a/spinnaker_csp/snn_creator.py +++ b/spinnaker_csp/snn_creator.py @@ -812,7 +812,7 @@ def projections_counter(projections_pop): # ).flatten() get_synapse_number = lambda x: x.get( "weight", "list", with_address=False - ).flatten() + ).reshape(-1) synapse_number = map(get_synapse_number, projections_pop) synapse_number = map(len, synapse_number) synapse_number = sum(synapse_number) From 2422fdc277507482fc9d812014980562f4465831 Mon Sep 17 00:00:00 2001 From: KOBAYASHI Takeshi Date: Fri, 22 Jul 2022 11:21:07 +0900 Subject: [PATCH 11/19] add exc constraint program --- spinnaker_csp/snn_creator.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/spinnaker_csp/snn_creator.py b/spinnaker_csp/snn_creator.py index 25a6367..efdc183 100644 --- a/spinnaker_csp/snn_creator.py +++ b/spinnaker_csp/snn_creator.py @@ -11,6 +11,7 @@ are either inhibitory or excitatory. The neurons are stochastically stimulated by spike sources implementing a Poisson process causing the network dynamics to implement a stochastic search of the satisying configuration. """ +from random import random import spynnaker8 as p # simulator from pyNN.random import RandomDistribution import numpy as np @@ -495,7 +496,13 @@ def apply_constraints( msg, """creating constraints between CSP variables with random and uniformelly distributed delays and weights""", ) - for constraint in self.constraints: + + if kind == "excitatory" and not random_cons: + applied_constraints = self.exc_constraints + else: + applied_constraints = self.constraints + + for constraint in applied_constraints: source = constraint["source"] target = constraint["target"] if random_cons: From 03bc986f8fd37b934d9ceb95f7a42e4ea4e2ff30 Mon Sep 17 00:00:00 2001 From: KOBAYASHI Takeshi Date: Fri, 22 Jul 2022 16:47:46 +0900 Subject: [PATCH 12/19] add return prob --- spinnaker_csp/analysis.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spinnaker_csp/analysis.py b/spinnaker_csp/analysis.py index 01bdeb8..f2b7b12 100644 --- a/spinnaker_csp/analysis.py +++ b/spinnaker_csp/analysis.py @@ -154,8 +154,8 @@ def plot_entropy( if TSP: with open("TSP_winners_matrix", "w") as file: sj.dump(p_max, file) - with open("results/entropy_%s.txt" % save_to, "w+") as file: - np.savetxt(file, [H, satisfiability]) + # with open("results/entropy_%s.txt" % save_to, "w+") as file: + # np.savetxt(file, [H, satisfiability]) # ------------------------- pop activity if not missing_matplotlib: if pop_activity: @@ -187,7 +187,7 @@ def plot_entropy( """ % (sol_time * resolution, visited_states[sol_time + 1], visited_states[-1]), ) - return sol + return sol, probabilities, p_max def load_data(prefix=None): From c40b26dc6519c427f1259ed8e8334861555ce30b Mon Sep 17 00:00:00 2001 From: KOBAYASHI Takeshi Date: Sat, 23 Jul 2022 21:41:20 +0900 Subject: [PATCH 13/19] add state constraint --- spinnaker_csp/snn_creator.py | 180 +++++++++++++++++++++++++++++------ 1 file changed, 153 insertions(+), 27 deletions(-) diff --git a/spinnaker_csp/snn_creator.py b/spinnaker_csp/snn_creator.py index efdc183..bf69d74 100644 --- a/spinnaker_csp/snn_creator.py +++ b/spinnaker_csp/snn_creator.py @@ -39,6 +39,7 @@ class CSP: stim_times = [] diss_times = [] constraint_conns = [] + state_constraint_conns = [] # Whether set_clues populations should receive inhibition from other sources. clues_inhibition = False # Parameters for the leaky integrate and fire neurons. @@ -58,8 +59,10 @@ def __init__( self, variables_number=0, domain_size=0, - constraints=[], + inh_constraints=[], exc_constraints=[], + inh_state_constraints=[], + exc_state_constraints=[], core_size=25, directed=False, run_time=30000, @@ -80,8 +83,10 @@ def __init__( self.core_size = core_size self.size = domain_size * core_size self.directed = directed - self.constraints = constraints + self.inh_constraints = inh_constraints self.exc_constraints = exc_constraints + self.inh_state_constraints = inh_state_constraints + self.exc_state_constraints = exc_state_constraints self.clues = [[]] self.run_time = run_time @@ -486,7 +491,7 @@ def apply_constraints( """ delays = RandomDistribution("uniform", d_range) weights = RandomDistribution("uniform", w_range) # 1.8 2.0 spin_system - if "weight" in self.constraints[0]: + if "weight" in self.inh_constraints[0]: print( msg, """creating constraints between CSP variables with specified weights and randomly distributed delays""", @@ -500,7 +505,7 @@ def apply_constraints( if kind == "excitatory" and not random_cons: applied_constraints = self.exc_constraints else: - applied_constraints = self.constraints + applied_constraints = self.inh_constraints for constraint in applied_constraints: source = constraint["source"] @@ -608,6 +613,115 @@ def apply_constraints( ) self.constraint_conns.append(synapses) + def apply_constraints_between_state( + self, + kind="inhibitory", + w_range=[-0.2, -0.0], + d_range=[2.0, 2.0], + random_cons=False, + pAF=0.5, + ): + """Map constraints list to inhibitory or excitatory connections between neural populations. + + The clues_inhibition class variable determines whether clues should receive inhibitory connections or not. + + args: + kind: whether constraints are inhibitory or excitatory. + w_range: range for the random distribution of synaptic weights in the form [w_min, w_max]. + d_range: range for the random distribution of synaptic delays in the form [d_min, d_max]. + random_cons: whether constraints are randomly choosen to be inhibitory or excitatory with probability pAF. + pAF: probability of inhibitory connections, as a probability it should be between 0.0 and 1.0. It only works + when random_cons is True. + """ + delays = RandomDistribution("uniform", d_range) + weights = RandomDistribution("uniform", w_range) # 1.8 2.0 spin_system + + if kind == "excitatory" and not random_cons: + applied_state_constraints = self.exc_state_constraints + else: + applied_state_constraints = self.inh_state_constraints + + if "weight" in applied_state_constraints[0]: + print( + msg, + """creating constraints between CSP variables with specified weights and randomly distributed delays""", + ) + else: + print( + msg, + """creating constraints between CSP variables with random and uniformelly distributed delays and weights""", + ) + + """ + constraint = [{"source": 0, "target": 1, "source_state": [0, 0, 1, 1, 1, 2, 2, 2, ...], "target_state": [0, 1, 0, 1, 2, 1, 2, 3, ...]}, {...}] + """ + + for constraint in applied_state_constraints: + source = constraint["source"] + source_states = constraint["source_state"] + target = constraint["target"] + target_states = constraint["target_state"] + if random_cons: + kind = np.random.choice(["inhibitory", "excitatory"], p=[pAF, 1 - pAF]) + if self.clues_inhibition: + for source_state, target_state in zip(source_states, target_states): + connections = [ + [ + m + source_state * self.core_size, + n + target_state * self.core_size, + constraint["weight"] + if "weight" in constraint + else weights.next(), + delays.next(), + ] + for n in range(self.core_size) + for m in range(self.core_size) + ] + synapses = p.Projection( + self.var_pops[source], + self.var_pops[target], + p.FromListConnector(connections, safe=True), + receptor_type=kind, + ) + self.state_constraint_conns.append(synapses) + if self.directed == False: + synapses = p.Projection( + self.var_pops[target], + self.var_pops[source], + p.FromListConnector(connections, safe=True), + receptor_type=kind, + ) + self.state_constraint_conns.append(synapses) + elif target not in self.clues[0]: + for source_state, target_state in zip(source_states, target_states): + connections = [ + [ + m + source_state * self.core_size, + n + target_state * self.core_size, + constraint["weight"] + if "weight" in constraint + else weights.next(), + delays.next(), + ] + for n in range(self.core_size) + for m in range(self.core_size) + ] + synapses = p.Projection( + self.var_pops[source], + self.var_pops[target], + p.FromListConnector(connections, safe=True), + receptor_type=kind, + ) + self.state_constraint_conns.append(synapses) + if self.directed == False: + synapses = p.Projection( + self.var_pops[target], + self.var_pops[source], + p.FromListConnector(connections, safe=True), + receptor_type=kind, + ) + self.state_constraint_conns.append(synapses) + def initialize(self, v_range=[-65.0, -55.0]): """Randomly initialize the membrane voltage of the neurons in the range v_range. @@ -678,7 +792,7 @@ def save(self, filename, DAT=False): self.size, self.domain_size, self.core_size, - self.constraints, + self.inh_constraints, self.stim_times, self.diss_times, ] @@ -803,27 +917,29 @@ def report_network_params(self, filename=False): net_neurons = var_neurons + stim_neurons + diss_neurons # Create function to count synapses from projections list. - def projections_counter(projections_pop): - """Count synapses from a list of projections. - - args: - projections_pop: list of projections, collected when calling other methods. - - returns: - synapse_number: the number of synapses generated by the list of projections. - """ - # synapse_number = sum(map(lambda x: len(x.getWeights('list').flatten()), projections_pop)) - - # synapse_number = lambda x: x.get( - # "weight", "list", with_address=False - # ).flatten() - get_synapse_number = lambda x: x.get( - "weight", "list", with_address=False - ).reshape(-1) - synapse_number = map(get_synapse_number, projections_pop) - synapse_number = map(len, synapse_number) - synapse_number = sum(synapse_number) - return synapse_number + # def projections_counter(projections_pop): + # """Count synapses from a list of projections. + + # args: + # projections_pop: list of projections, collected when calling other methods. + + # returns: + # synapse_number: the number of synapses generated by the list of projections. + # """ + # # synapse_number = sum(map(lambda x: len(x.getWeights('list').flatten()), projections_pop)) + + # # synapse_number = lambda x: x.get( + # # "weight", "list", with_address=False + # # ).flatten() + # get_synapse_number = lambda x: x.get( + # "weight", "list", with_address=False + # ).reshape(-1) + # synapse_number = map(get_synapse_number, projections_pop) + # synapse_number = map(len, synapse_number) + # synapse_number = sum(synapse_number) + # return synapse_number + def projections_counter(projections): + return sum(len(proj.get("weight", "list")) for proj in projections) # Count synapses created by methods that connected neural populations. core_conns = projections_counter(self.core_conns) @@ -831,8 +947,16 @@ def projections_counter(projections_pop): stim_conns = projections_counter(self.stim_conns) diss_conns = projections_counter(self.diss_conns) constraint_conns = projections_counter(self.constraint_conns) + state_constraint_conns = projections_counter(self.state_constraint_conns) net_conns = sum( - [core_conns, internal_conns, stim_conns, diss_conns, constraint_conns] + [ + core_conns, + internal_conns, + stim_conns, + diss_conns, + constraint_conns, + state_constraint_conns, + ] ) # Report template. @@ -855,6 +979,7 @@ def projections_counter(projections_pop): | stimulating synapses: %d | dissipating synapses: %d | constraints synapses: %d + | st constraints synapses: %d | var internal synapses: %d | core internal synapses: %d |=====================================| @@ -873,6 +998,7 @@ def projections_counter(projections_pop): stim_conns, diss_conns, constraint_conns, + state_constraint_conns, internal_conns, core_conns, ) From cef54463c55b29d9109ad271a20b00638e1a0435 Mon Sep 17 00:00:00 2001 From: KOBAYASHI Takeshi Date: Sun, 24 Jul 2022 19:10:01 +0900 Subject: [PATCH 14/19] add inhibitory clues --- spinnaker_csp/snn_creator.py | 75 ++++++++++++++++++++++++++++-------- 1 file changed, 58 insertions(+), 17 deletions(-) diff --git a/spinnaker_csp/snn_creator.py b/spinnaker_csp/snn_creator.py index bf69d74..69b17ab 100644 --- a/spinnaker_csp/snn_creator.py +++ b/spinnaker_csp/snn_creator.py @@ -87,10 +87,11 @@ def __init__( self.exc_constraints = exc_constraints self.inh_state_constraints = inh_state_constraints self.exc_state_constraints = exc_state_constraints - self.clues = [[]] + self.exc_clues = [[]] + self.inh_clues = [[]] self.run_time = run_time - def set_clues(self, clues): + def set_clues(self, exc_clues=None, inh_clues=None): """Take set_clues as an array of the form [[list of variables],[list of values]]. Here clues are fixed and predetermined values for particular variables, These influence the constraints @@ -99,7 +100,8 @@ def set_clues(self, clues): args: clues: an array of the form [[list of variable ids],[list of values taken by those variables]] """ - self.clues = clues + self.exc_clues = exc_clues + self.inh_clues = inh_clues def build_domains_pops(self): """Generate an array of pyNN population objects, one for each CSP variable. @@ -245,7 +247,7 @@ def build_stimulation_pops( label="stim%d_var%d" % (stimulus + 1, variable + 1), ) ) - if variable in self.clues[0]: + if variable in self.exc_clues[0]: clues_stim.append( p.Population( clue_size, @@ -265,9 +267,10 @@ def build_dissipation_pops( d_populations=1, shrink=1.0, stim_ratio=1.0, - rate=20.0, + rate=(20.0, 20.0), full=True, phase=0.0, + clue_size=None, ): """Generate noise sinks for each neuron: pyNN population objects of the type SpikeSourcePoisson. @@ -281,10 +284,12 @@ def build_dissipation_pops( It defines fraction of the run time, so it should be between 0.0 and 1.0. stim_ratio: defines the portion of the depression window in which the noise will be active. A value of 0.5 will mean that depression happens only during the first half of the interval. - rate: a floating-point number defining the rate of the Poisson process for the noise - populations. + rate: a tuple of floating-point numbers defining the rate of the Poisson process for the noise + populations, the first value is used for all CSP variable populations and the second value for + the clues. full: controls if the noise deactivations should all happen after run_time or at the lapso width. phase: a waiting time before the first dissipation population activates. + clue_size: optional, number of neurons to use to stimulate clues, default value is core_size. """ print( msg, @@ -294,7 +299,10 @@ def build_dissipation_pops( diss_times, comienza, termina = self.poisson_params( d_populations, full=full, stim_ratio=stim_ratio, shrink=shrink, phase=phase ) + if clue_size == None: + clue_size = self.core_size diss_pops = [[] for k in range(d_populations)] + clues_diss = [] for k in range(d_populations): for variable in range(self.variables_number): diss_pops[k].append( @@ -302,17 +310,28 @@ def build_dissipation_pops( self.size, p.SpikeSourcePoisson, { - "rate": rate, + "rate": rate[0], "start": comienza[k].next(), "duration": termina[k].next(), }, label="diss%d_var%d" % (k + 1, variable + 1), ) ) + if variable in self.inh_clues[0]: + clues_diss.append( + p.Population( + clue_size, + p.SpikeSourcePoisson, + {"rate": rate[1], "start": 0, "duration": self.run_time}, + label="clues_diss%d" % variable, + ) + ) # TODO: if self.clues_inibition = False do not create the populations for the clues. self.diss_pops = diss_pops + self.clues_diss = clues_diss self.d_populations = d_populations self.disss = diss_times + self.clues_size = clue_size def connect_cores(self, w_range=[0.6, 1.2], d_range=[1.0, 1.2]): """Create internal excitatory connections between the neurons of each domain subpopulation of each variable. @@ -381,7 +400,7 @@ def internal_inhibition(self, w_range=[-0.2, 0.0], d_range=[2.0, 2.0]): receptor_type="inhibitory", ) self.internal_conns.append(synapses) - elif variable not in self.clues: + elif variable not in self.exc_clues: synapses = p.Projection( self.var_pops[variable], self.var_pops[variable], @@ -411,9 +430,10 @@ def stimulate_cores( for stimulus in range(self.n_populations): for variable in range(self.variables_number): counter = 0 - if variable in self.clues[0]: + if variable in self.exc_clues[0]: shift = ( - self.clues[1][self.clues[0].index(variable)] * self.core_size + self.exc_clues[1][self.exc_clues[0].index(variable)] + * self.core_size ) connections = [ (m, n + shift, weight_clues.next(), delays.next()) @@ -428,7 +448,7 @@ def stimulate_cores( ) counter += 1 self.stim_conns.append(synapses) - else: + elif variable not in self.inh_clues[0]: synapses = p.Projection( self.stim_pops[stimulus][variable], self.var_pops[variable], @@ -441,12 +461,13 @@ def stimulate_cores( self.stim_conns.append(synapses) self.stim_times += self.stims - def depress_cores(self, w_range=[-2.0, -1.5], d_range=[2.0, 2.0]): + def depress_cores(self, w_range=[1.4, 1.4], d_range=[1.0, 1.0], w_clues=[1.4, 1.6]): """Connect depressing noise sources to variables populations. args: w_range: range for the random distribution of synaptic weights in the form [w_min, w_max]. d_range: range for the random distribution of synaptic delays in the form [d_min, d_max]. + w_clues: clues specific range for the random distribution of synaptic weights in the form [w_min, w_max]. """ print( msg, @@ -454,9 +475,29 @@ def depress_cores(self, w_range=[-2.0, -1.5], d_range=[2.0, 2.0]): ) delays = RandomDistribution("uniform", d_range) weights = RandomDistribution("uniform", w_range) + weight_clues = RandomDistribution("uniform", w_clues) for depressor in range(self.d_populations): for variable in range(self.variables_number): - if variable not in self.clues[0]: + counter = 0 + if variable in self.inh_clues[0]: + shift = ( + self.inh_clues[1][self.inh_clues[0].index(variable)] + * self.core_size + ) + connections = [ + (m, n + shift, weight_clues.next(), delays.next()) + for m in range(self.core_size) + for n in range(self.clue_size) + ] + synapses = p.Projection( + self.clues_diss[counter], + self.var_pops[variable], + p.FromListConnector(connections, safe=True), + receptor_type="inhibitory", + ) + counter += 1 + self.diss_conns.append(synapses) + elif variable not in self.exc_clues[0]: synapses = p.Projection( self.diss_pops[depressor][variable], self.var_pops[variable], @@ -563,7 +604,7 @@ def apply_constraints( receptor_type=kind, ) self.constraint_conns.append(synapses) - elif target not in self.clues[0]: + elif target not in self.exc_clues[0]: # connections = [] connections = [ [ @@ -692,7 +733,7 @@ def apply_constraints_between_state( receptor_type=kind, ) self.state_constraint_conns.append(synapses) - elif target not in self.clues[0]: + elif target not in self.exc_clues[0]: for source_state, target_state in zip(source_states, target_states): connections = [ [ @@ -907,7 +948,7 @@ def report_network_params(self, filename=False): if not os.path.exists("results"): os.makedirs("results") var_pops_num = len(self.var_pops) - diss_pops_num = len(self.diss_pops) + diss_pops_num = len(self.diss_pops) * len(self.diss_pops[0]) stim_pops_num = len(self.stim_pops) * len(self.stim_pops[0]) pops_number = var_pops_num + diss_pops_num + stim_pops_num # Count neurons. From 88aa3532f4bd209f3115c21e5f654bebb375ab95 Mon Sep 17 00:00:00 2001 From: KOBAYASHI Takeshi Date: Sun, 24 Jul 2022 22:56:06 +0900 Subject: [PATCH 15/19] delete comment out --- spinnaker_csp/snn_creator.py | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/spinnaker_csp/snn_creator.py b/spinnaker_csp/snn_creator.py index 69b17ab..42c597c 100644 --- a/spinnaker_csp/snn_creator.py +++ b/spinnaker_csp/snn_creator.py @@ -957,28 +957,6 @@ def report_network_params(self, filename=False): diss_neurons = diss_pops_num * self.size net_neurons = var_neurons + stim_neurons + diss_neurons - # Create function to count synapses from projections list. - # def projections_counter(projections_pop): - # """Count synapses from a list of projections. - - # args: - # projections_pop: list of projections, collected when calling other methods. - - # returns: - # synapse_number: the number of synapses generated by the list of projections. - # """ - # # synapse_number = sum(map(lambda x: len(x.getWeights('list').flatten()), projections_pop)) - - # # synapse_number = lambda x: x.get( - # # "weight", "list", with_address=False - # # ).flatten() - # get_synapse_number = lambda x: x.get( - # "weight", "list", with_address=False - # ).reshape(-1) - # synapse_number = map(get_synapse_number, projections_pop) - # synapse_number = map(len, synapse_number) - # synapse_number = sum(synapse_number) - # return synapse_number def projections_counter(projections): return sum(len(proj.get("weight", "list")) for proj in projections) From 28de1f86c57f765bb63465ad29dde137bbe65f3f Mon Sep 17 00:00:00 2001 From: KOBAYASHI Takeshi Date: Mon, 25 Jul 2022 01:16:32 +0900 Subject: [PATCH 16/19] add multiple clues synapse --- spinnaker_csp/snn_creator.py | 70 +++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/spinnaker_csp/snn_creator.py b/spinnaker_csp/snn_creator.py index 42c597c..fae72b7 100644 --- a/spinnaker_csp/snn_creator.py +++ b/spinnaker_csp/snn_creator.py @@ -431,23 +431,26 @@ def stimulate_cores( for variable in range(self.variables_number): counter = 0 if variable in self.exc_clues[0]: - shift = ( - self.exc_clues[1][self.exc_clues[0].index(variable)] - * self.core_size - ) - connections = [ - (m, n + shift, weight_clues.next(), delays.next()) - for m in range(self.core_size) - for n in range(self.clue_size) + clue_states = [ + self.exc_clues[1][i] + for i, v in enumerate(self.exc_clues[0]) + if v == variable ] - synapses = p.Projection( - self.clues_stim[counter], - self.var_pops[variable], - p.FromListConnector(connections, safe=True), - receptor_type="excitatory", - ) - counter += 1 - self.stim_conns.append(synapses) + for clue_state in clue_states: + shift = clue_state * self.core_size + connections = [ + (m, n + shift, weight_clues.next(), delays.next()) + for m in range(self.core_size) + for n in range(self.clue_size) + ] + synapses = p.Projection( + self.clues_stim[counter], + self.var_pops[variable], + p.FromListConnector(connections, safe=True), + receptor_type="excitatory", + ) + counter += 1 + self.stim_conns.append(synapses) elif variable not in self.inh_clues[0]: synapses = p.Projection( self.stim_pops[stimulus][variable], @@ -480,23 +483,26 @@ def depress_cores(self, w_range=[1.4, 1.4], d_range=[1.0, 1.0], w_clues=[1.4, 1. for variable in range(self.variables_number): counter = 0 if variable in self.inh_clues[0]: - shift = ( - self.inh_clues[1][self.inh_clues[0].index(variable)] - * self.core_size - ) - connections = [ - (m, n + shift, weight_clues.next(), delays.next()) - for m in range(self.core_size) - for n in range(self.clue_size) + clue_states = [ + self.inh_clues[1][i] + for i, v in enumerate(self.inh_clues[0]) + if v == variable ] - synapses = p.Projection( - self.clues_diss[counter], - self.var_pops[variable], - p.FromListConnector(connections, safe=True), - receptor_type="inhibitory", - ) - counter += 1 - self.diss_conns.append(synapses) + for clue_state in clue_states: + shift = clue_state * self.core_size + connections = [ + (m, n + shift, weight_clues.next(), delays.next()) + for m in range(self.core_size) + for n in range(self.clue_size) + ] + synapses = p.Projection( + self.clues_diss[counter], + self.var_pops[variable], + p.FromListConnector(connections, safe=True), + receptor_type="inhibitory", + ) + counter += 1 + self.diss_conns.append(synapses) elif variable not in self.exc_clues[0]: synapses = p.Projection( self.diss_pops[depressor][variable], From 99887269c7843752484ea020c1b44eea2fa4d56e Mon Sep 17 00:00:00 2001 From: KOBAYASHI Takeshi Date: Mon, 25 Jul 2022 01:44:21 +0900 Subject: [PATCH 17/19] add multiple clue neurons --- spinnaker_csp/snn_creator.py | 48 +++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/spinnaker_csp/snn_creator.py b/spinnaker_csp/snn_creator.py index fae72b7..5025e97 100644 --- a/spinnaker_csp/snn_creator.py +++ b/spinnaker_csp/snn_creator.py @@ -248,14 +248,24 @@ def build_stimulation_pops( ) ) if variable in self.exc_clues[0]: - clues_stim.append( - p.Population( - clue_size, - p.SpikeSourcePoisson, - {"rate": rate[1], "start": 0, "duration": self.run_time}, - label="clues_stim%d" % variable, + clue_states = [ + self.exc_clues[1][i] + for i, v in enumerate(self.exc_clues[0]) + if v == variable + ] + for clue_state in clue_states: + clues_stim.append( + p.Population( + clue_size, + p.SpikeSourcePoisson, + { + "rate": rate[1], + "start": 0, + "duration": self.run_time, + }, + label="clues_stim{}_{}".format(variable, clue_state), + ) ) - ) self.stim_pops = stim_pops self.clues_stim = clues_stim self.n_populations = n_populations @@ -318,14 +328,24 @@ def build_dissipation_pops( ) ) if variable in self.inh_clues[0]: - clues_diss.append( - p.Population( - clue_size, - p.SpikeSourcePoisson, - {"rate": rate[1], "start": 0, "duration": self.run_time}, - label="clues_diss%d" % variable, + clue_states = [ + self.inh_clues[1][i] + for i, v in enumerate(self.inh_clues[0]) + if v == variable + ] + for clue_state in clue_states: + clues_diss.append( + p.Population( + clue_size, + p.SpikeSourcePoisson, + { + "rate": rate[1], + "start": 0, + "duration": self.run_time, + }, + label="clues_diss{}_{}".format(variable, clue_state), + ) ) - ) # TODO: if self.clues_inibition = False do not create the populations for the clues. self.diss_pops = diss_pops self.clues_diss = clues_diss From 1232ba655f5e6a43d040de4f190a1eb92b5c4ca5 Mon Sep 17 00:00:00 2001 From: takepiyo Date: Mon, 1 Aug 2022 19:39:23 +0900 Subject: [PATCH 18/19] some general revise --- examples/sudoku.py | 8 +++--- spinnaker_csp/snn_creator.py | 50 ++++++++++++++++++++++++------------ 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/examples/sudoku.py b/examples/sudoku.py index 365c79e..e8ae9d1 100644 --- a/examples/sudoku.py +++ b/examples/sudoku.py @@ -52,7 +52,7 @@ sk = sudoku2csp(grid) # Build spiking neural network. sudoku = CSP( - sk.var_num, sk.dom_num, sk.constraints, core_size=5, run_time=run_time + sk.var_num, sk.dom_num, sk.constraints, core_size=25, run_time=run_time ) # create csp instance sudoku.clues_inhibition = True sudoku.set_clues(sk.clues) @@ -63,7 +63,7 @@ sudoku.stimulate_cores( w_range=[1.4, 1.6], d_range=[1.0, 1.0], w_clues=[1.8, 2.0] ) # , w_clues=[1.5, 1.9]) -# sudoku.apply_constraints(w_range=[-0.2 / 2.5, 0.0]) +sudoku.apply_constraints(kind="inhibitory", w_range=[-0.2 / 2.5, 0.0]) sudoku.initialize() # Record spikes from variable populations. sudoku.recording() @@ -75,7 +75,7 @@ # End simulation. p.end() # Plot entropy. -sol = plot_entropy(name, 200, show=False) +sol, probabilities, p_max = plot_entropy(name, 200, show=False) # Show solution on std output. if sol is not None: for i in range(len(sol)): @@ -83,5 +83,5 @@ print("=" * 70) n = 0 for i in range(9): - print(sol[n : n + 9]) + print(sol[n: n + 9]) n += 9 diff --git a/spinnaker_csp/snn_creator.py b/spinnaker_csp/snn_creator.py index 5025e97..03b7f64 100644 --- a/spinnaker_csp/snn_creator.py +++ b/spinnaker_csp/snn_creator.py @@ -17,7 +17,8 @@ import numpy as np import os -msg = "%s \n" % ("=" * 70) # a separator for readability of messages on standard output +# a separator for readability of messages on standard output +msg = "%s \n" % ("=" * 70) class CSP: @@ -91,7 +92,7 @@ def __init__( self.inh_clues = [[]] self.run_time = run_time - def set_clues(self, exc_clues=None, inh_clues=None): + def set_clues(self, exc_clues=[[]], inh_clues=[[]]): """Take set_clues as an array of the form [[list of variables],[list of values]]. Here clues are fixed and predetermined values for particular variables, These influence the constraints @@ -110,7 +111,8 @@ def build_domains_pops(self): var_pops[i] is the population for variable i including all domain sub-populations, each of size core_size. """ print( - msg, "creating an array of %d neural populations" % (self.variables_number) + msg, "creating an array of %d neural populations" % ( + self.variables_number) ) var_pops = [] for variable in range(self.variables_number): @@ -174,7 +176,8 @@ def poisson_params( ] if full: termina = [ - RandomDistribution("uniform", [self.run_time - delta, self.run_time]) + RandomDistribution( + "uniform", [self.run_time - delta, self.run_time]) for i in range(n_populations) ] else: @@ -263,7 +266,8 @@ def build_stimulation_pops( "start": 0, "duration": self.run_time, }, - label="clues_stim{}_{}".format(variable, clue_state), + label="clues_stim{}_{}".format( + variable, clue_state), ) ) self.stim_pops = stim_pops @@ -343,7 +347,8 @@ def build_dissipation_pops( "start": 0, "duration": self.run_time, }, - label="clues_diss{}_{}".format(variable, clue_state), + label="clues_diss{}_{}".format( + variable, clue_state), ) ) # TODO: if self.clues_inibition = False do not create the populations for the clues. @@ -578,7 +583,8 @@ def apply_constraints( source = constraint["source"] target = constraint["target"] if random_cons: - kind = np.random.choice(["inhibitory", "excitatory"], p=[pAF, 1 - pAF]) + kind = np.random.choice( + ["inhibitory", "excitatory"], p=[pAF, 1 - pAF]) # TODO find a way of reducing the next two conditionals, they're equal except for conditioning on target... # TODO ... being a clue. if self.clues_inhibition: @@ -729,7 +735,8 @@ def apply_constraints_between_state( target = constraint["target"] target_states = constraint["target_state"] if random_cons: - kind = np.random.choice(["inhibitory", "excitatory"], p=[pAF, 1 - pAF]) + kind = np.random.choice( + ["inhibitory", "excitatory"], p=[pAF, 1 - pAF]) if self.clues_inhibition: for source_state, target_state in zip(source_states, target_states): connections = [ @@ -869,10 +876,12 @@ def save(self, filename, DAT=False): np.concatenate( [ np.stack( - [i * np.ones_like(spike.magnitude), spike.magnitude] + [i * np.ones_like(spike.magnitude), + spike.magnitude] ).transpose() for i, spike in enumerate( - population.get_data("spikes").segments[0].spiketrains + population.get_data( + "spikes").segments[0].spiketrains ) ], axis=0, @@ -911,10 +920,12 @@ def save_stimulation(self, filename, DAT=False): np.concatenate( [ np.stack( - [i * np.ones_like(spike.magnitude), spike.magnitude] + [i * np.ones_like(spike.magnitude), + spike.magnitude] ).transpose() for i, spike in enumerate( - population.get_data("spikes").segments[0].spiketrains + population.get_data( + "spikes").segments[0].spiketrains ) ], axis=0, @@ -952,10 +963,12 @@ def save_dissipation(self, filename, DAT=False): np.concatenate( [ np.stack( - [i * np.ones_like(spike.magnitude), spike.magnitude] + [i * np.ones_like(spike.magnitude), + spike.magnitude] ).transpose() for i, spike in enumerate( - population.get_data("spikes").segments[0].spiketrains + population.get_data( + "spikes").segments[0].spiketrains ) ], axis=0, @@ -974,8 +987,10 @@ def report_network_params(self, filename=False): if not os.path.exists("results"): os.makedirs("results") var_pops_num = len(self.var_pops) - diss_pops_num = len(self.diss_pops) * len(self.diss_pops[0]) - stim_pops_num = len(self.stim_pops) * len(self.stim_pops[0]) + diss_pops_num = 0 if len(self.diss_pops[0]) == 0 else len( + self.diss_pops) * len(self.diss_pops[0]) + stim_pops_num = 0 if len(self.stim_pops[0]) == 0 else len( + self.stim_pops) * len(self.stim_pops[0]) pops_number = var_pops_num + diss_pops_num + stim_pops_num # Count neurons. var_neurons = var_pops_num * self.size @@ -992,7 +1007,8 @@ def projections_counter(projections): stim_conns = projections_counter(self.stim_conns) diss_conns = projections_counter(self.diss_conns) constraint_conns = projections_counter(self.constraint_conns) - state_constraint_conns = projections_counter(self.state_constraint_conns) + state_constraint_conns = projections_counter( + self.state_constraint_conns) net_conns = sum( [ core_conns, From 3e8d2a025c314e25a1e5d979e7ce5f1c4fb7d461 Mon Sep 17 00:00:00 2001 From: takepiyo Date: Mon, 5 Dec 2022 15:41:40 +0900 Subject: [PATCH 19/19] revise clues --- spinnaker_csp/snn_creator.py | 72 ++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 32 deletions(-) diff --git a/spinnaker_csp/snn_creator.py b/spinnaker_csp/snn_creator.py index 03b7f64..0139319 100644 --- a/spinnaker_csp/snn_creator.py +++ b/spinnaker_csp/snn_creator.py @@ -195,7 +195,7 @@ def build_stimulation_pops( n_populations=1, shrink=1.0, stim_ratio=1.0, - rate=(20.0, 20.0), + rate=(20.0, 50.0), full=True, phase=0.0, clue_size=None, @@ -232,8 +232,9 @@ def build_stimulation_pops( stim_times, comienza, termina = self.poisson_params( n_populations, full=full, stim_ratio=stim_ratio, shrink=shrink, phase=phase ) - if clue_size == None: - clue_size = self.core_size + self.clue_size = clue_size + if self.clue_size == None: + self.clue_size = self.core_size stim_pops = [[] for k in range(n_populations)] clues_stim = [] for stimulus in range(n_populations): @@ -259,7 +260,7 @@ def build_stimulation_pops( for clue_state in clue_states: clues_stim.append( p.Population( - clue_size, + self.clue_size, p.SpikeSourcePoisson, { "rate": rate[1], @@ -281,7 +282,7 @@ def build_dissipation_pops( d_populations=1, shrink=1.0, stim_ratio=1.0, - rate=(20.0, 20.0), + rate=(20.0, 50.0), full=True, phase=0.0, clue_size=None, @@ -313,8 +314,9 @@ def build_dissipation_pops( diss_times, comienza, termina = self.poisson_params( d_populations, full=full, stim_ratio=stim_ratio, shrink=shrink, phase=phase ) - if clue_size == None: - clue_size = self.core_size + self.clue_size = clue_size + if self.clue_size == None: + self.clue_size = self.core_size diss_pops = [[] for k in range(d_populations)] clues_diss = [] for k in range(d_populations): @@ -340,7 +342,7 @@ def build_dissipation_pops( for clue_state in clue_states: clues_diss.append( p.Population( - clue_size, + self.clue_size, p.SpikeSourcePoisson, { "rate": rate[1], @@ -454,6 +456,16 @@ def stimulate_cores( weight_clues = RandomDistribution("uniform", w_clues) for stimulus in range(self.n_populations): for variable in range(self.variables_number): + synapses = p.Projection( + self.stim_pops[stimulus][variable], + self.var_pops[variable], + p.OneToOneConnector(), + synapse_type=p.StaticSynapse( + weight=weights, delay=delays.next() + ), + receptor_type="excitatory", + ) + self.stim_conns.append(synapses) counter = 0 if variable in self.exc_clues[0]: clue_states = [ @@ -476,17 +488,6 @@ def stimulate_cores( ) counter += 1 self.stim_conns.append(synapses) - elif variable not in self.inh_clues[0]: - synapses = p.Projection( - self.stim_pops[stimulus][variable], - self.var_pops[variable], - p.OneToOneConnector(), - synapse_type=p.StaticSynapse( - weight=weights, delay=delays.next() - ), - receptor_type="excitatory", - ) - self.stim_conns.append(synapses) self.stim_times += self.stims def depress_cores(self, w_range=[1.4, 1.4], d_range=[1.0, 1.0], w_clues=[1.4, 1.6]): @@ -506,6 +507,16 @@ def depress_cores(self, w_range=[1.4, 1.4], d_range=[1.0, 1.0], w_clues=[1.4, 1. weight_clues = RandomDistribution("uniform", w_clues) for depressor in range(self.d_populations): for variable in range(self.variables_number): + synapses = p.Projection( + self.diss_pops[depressor][variable], + self.var_pops[variable], + p.OneToOneConnector(), + synapse_type=p.StaticSynapse( + weight=weights, delay=delays.next() + ), + receptor_type="inhibitory", + ) + self.diss_conns.append(synapses) counter = 0 if variable in self.inh_clues[0]: clue_states = [ @@ -528,17 +539,6 @@ def depress_cores(self, w_range=[1.4, 1.4], d_range=[1.0, 1.0], w_clues=[1.4, 1. ) counter += 1 self.diss_conns.append(synapses) - elif variable not in self.exc_clues[0]: - synapses = p.Projection( - self.diss_pops[depressor][variable], - self.var_pops[variable], - p.OneToOneConnector(), - synapse_type=p.StaticSynapse( - weight=weights, delay=delays.next() - ), - receptor_type="inhibitory", - ) - self.diss_conns.append(synapses) self.diss_times += self.disss def apply_constraints( @@ -991,12 +991,20 @@ def report_network_params(self, filename=False): self.diss_pops) * len(self.diss_pops[0]) stim_pops_num = 0 if len(self.stim_pops[0]) == 0 else len( self.stim_pops) * len(self.stim_pops[0]) - pops_number = var_pops_num + diss_pops_num + stim_pops_num + clue_stim_pop_num = 0 if len( + self.clues_stim) == 0 else len(self.clues_stim) + clue_diss_pop_num = 0 if len( + self.clues_diss) == 0 else len(self.clues_diss) + pops_number = var_pops_num + diss_pops_num + \ + stim_pops_num + clue_stim_pop_num + clue_diss_pop_num # Count neurons. var_neurons = var_pops_num * self.size stim_neurons = stim_pops_num * self.size diss_neurons = diss_pops_num * self.size - net_neurons = var_neurons + stim_neurons + diss_neurons + clue_stim_neurons = clue_stim_pop_num * self.clue_size + clue_diss_neurons = clue_diss_pop_num * self.clue_size + net_neurons = var_neurons + stim_neurons + \ + diss_neurons + clue_stim_neurons + clue_diss_neurons def projections_counter(projections): return sum(len(proj.get("weight", "list")) for proj in projections)