sango 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
sango/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ from .core import NodeGroup, EdgeGroup, NodePort, NodeList
2
+ from .network import Network
3
+
4
+ __version__ = "1.0.0"
@@ -0,0 +1,3 @@
1
+ from .stacs.stacs import SimSTACS
2
+ from .brian.brian import SimBrian
3
+ from .fugu.fugu import SimFugu
File without changes
@@ -0,0 +1,292 @@
1
+ import os
2
+ import sys
3
+ from pathlib import Path
4
+ import importlib.util
5
+
6
+ try:
7
+ import brian2
8
+ except ImportError:
9
+ brian2 = None
10
+ if brian2 is not None:
11
+ from brian2 import NeuronGroup, Synapses, SpikeGeneratorGroup, SpikeMonitor
12
+ from brian2 import ms, defaultclock
13
+
14
+ import time
15
+ from collections import Counter
16
+
17
+ import numpy as np
18
+ import matplotlib.pyplot as plt
19
+
20
+ # Brian Simulation Backend
21
+ class SimBrian:
22
+ def __init__(self, dsl_net):
23
+ if brian2 is None:
24
+ raise ImportError("brian2 package is required for SimBrian")
25
+
26
+ self.dsl_net = dsl_net
27
+ self.tstep = 1.0*ms
28
+ self.timesteps = None
29
+
30
+ self.node_map = None
31
+ self.spike_list = None
32
+
33
+ self.model_registry = self.import_registry()
34
+
35
+ # Dynamically import the model registry files
36
+ def import_registry(self):
37
+ registry = dict()
38
+ registry_dir = Path(__file__).resolve().parent / 'registry'
39
+ sys.path.insert(0, str(registry_dir.parent))
40
+ for file_path in registry_dir.glob("*.py"):
41
+ module_name = f"registry.{file_path.stem}"
42
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
43
+ module = importlib.util.module_from_spec(spec)
44
+ spec.loader.exec_module(module)
45
+ registry.update(module.model_registry)
46
+ sys.path.pop(0)
47
+ return registry
48
+
49
+ # Convert between dsl model to brian2 model
50
+ def rekey_model(self, data):
51
+ for key, value in self.model_registry[data['model']]['state'].items():
52
+ if value['dsl'] is not None:
53
+ data[key] = data.pop(value['dsl'])
54
+ else:
55
+ data[key] = value['default']
56
+ return data
57
+
58
+ def compile(self, debug=False):
59
+ self.debug = debug
60
+
61
+ # Convert network to brian
62
+ start_time = time.perf_counter()
63
+ self.to_brian()
64
+ end_time = time.perf_counter()
65
+ self.compile_time = end_time - start_time
66
+ if self.debug:
67
+ print(f"Compile time: {self.compile_time}")
68
+
69
+ def run(self, timesteps=10.0, verbose=False):
70
+ self.timesteps = timesteps*self.tstep
71
+ self.verbose = verbose
72
+
73
+ # Run network
74
+ start_time = time.perf_counter()
75
+ self.brian_net.run(self.timesteps)
76
+ end_time = time.perf_counter()
77
+ self.run_time = end_time - start_time
78
+ if self.verbose:
79
+ print(f"Run time: {self.run_time}")
80
+
81
+ def to_brian(self):
82
+ # Get a flattened graph object
83
+ self.dsl_graph = self.dsl_net._topology.to_nx()
84
+ defaultclock.dt = 1.0*ms
85
+
86
+ # Convert to Brian2 network objects
87
+ self.num_nodes = self.dsl_graph.number_of_nodes()
88
+ self.node_index = dict()
89
+ self.node_data = [dict() for _ in range(self.num_nodes)]
90
+ self.edge_data = [dict() for _ in range(self.num_nodes)]
91
+ self.group_count = Counter()
92
+ self.local_index = [0 for _ in range(self.num_nodes)]
93
+ self.edge_set = set()
94
+ self.spike_input = []
95
+
96
+ # Add input model (IN) to the beginning of the group
97
+ self.group_count['IN'] = 0
98
+
99
+ # Global node data
100
+ for n, (node, data) in enumerate(self.dsl_graph.nodes(data=True)):
101
+ self.node_index[node] = n
102
+ self.node_data[n] = self.rekey_model(data)
103
+ self.group_count.update([self.node_data[n]['model']])
104
+ self.local_index[n] = self.group_count[self.node_data[n]['model']] - 1
105
+ if self.model_registry[self.node_data[n]['model']]['graph_type'] == 'input':
106
+ self.spike_input.append(data['times'])
107
+
108
+ # Remove the input model (IN) if counts are zero
109
+ if self.group_count['IN'] == 0:
110
+ del self.group_count['IN']
111
+
112
+ # Get the model insertion order of our counter, and organize node map by group
113
+ self.group_index = {key: index for index, key in enumerate(self.group_count.keys())}
114
+ self.group_sorted_count = [self.group_count[group] for group in self.group_index]
115
+ self.group_offset = [0] + [sum(self.group_sorted_count[:i+1]) for i in range(len(self.group_sorted_count))]
116
+ self.node_map = {key: self.group_offset[self.group_index[self.node_data[n]['model']]] + self.local_index[n]
117
+ for key, n in self.node_index.items()}
118
+
119
+ # Global edge data
120
+ for source, target, data in self.dsl_graph.edges(data=True):
121
+ s = self.node_index[source]
122
+ t = self.node_index[target]
123
+ self.edge_data[s][t] = self.rekey_model(data)
124
+ self.edge_set.add((self.edge_data[s][t]['model'], self.node_data[s]['model'], self.node_data[t]['model']))
125
+
126
+ # Spike generator inputs (sorted)
127
+ if not self.spike_input:
128
+ pass
129
+ else:
130
+ spike_index = []
131
+ spike_times = []
132
+ for i, times in enumerate(self.spike_input):
133
+ for t in times:
134
+ spike_index.append(i)
135
+ spike_times.append(t*ms)
136
+ self.spikegen_times, self.spikegen_index = [list(t) for t in zip(*sorted(zip(spike_times, spike_index)))]
137
+
138
+ # Container for local neuron states (by group)
139
+ self.neuron_states = dict()
140
+ for name in self.group_count.keys():
141
+ self.neuron_states[name] = dict()
142
+ for state in self.model_registry[name]['state']:
143
+ self.neuron_states[name][state] = []
144
+
145
+ # Container for local synapse states (by connection)
146
+ self.synapse_connections = {f"{n}_{s}_{t}": {'i': [], 'j': []} for (n,s,t) in self.edge_set}
147
+ self.synapse_states = dict()
148
+ for name, source, target in self.edge_set:
149
+ full_name = f"{name}_{source}_{target}"
150
+ self.synapse_states[full_name] = dict()
151
+ for state in self.model_registry[name]['state']:
152
+ self.synapse_states[full_name][state] = []
153
+
154
+ # Neurons
155
+ for n, data in enumerate(self.node_data):
156
+ name = data['model']
157
+ for state in self.model_registry[name]['state']:
158
+ self.neuron_states[name][state].append(data[state])
159
+
160
+ # Synapses
161
+ for s in range(self.num_nodes):
162
+ for t, data in self.edge_data[s].items():
163
+ full_name = f"{data['model']}_{self.node_data[s]['model']}_{self.node_data[t]['model']}"
164
+ self.synapse_connections[full_name]['i'].append(self.local_index[s])
165
+ self.synapse_connections[full_name]['j'].append(self.local_index[t])
166
+ for state in self.model_registry[data['model']]['state']:
167
+ if state == 'delay':
168
+ # Brian has a default delay of 0ms (to get to the next timestep)
169
+ self.synapse_states[full_name]['delay'].append((data['delay']-1.0)*ms)
170
+ else:
171
+ self.synapse_states[full_name][state].append(data[state])
172
+
173
+ # Brian Network
174
+ self.brian_net = brian2.Network()
175
+ self.input_groups = dict()
176
+ self.neuron_groups = dict()
177
+ self.synapse_groups = dict()
178
+ self.spike_monitors = dict()
179
+
180
+ # Create input and neuron groups (and their spike monitors)
181
+ for name, count in self.group_count.items():
182
+ # Spike generator group
183
+ if self.model_registry[name]['graph_type'] == 'input':
184
+ self.input_groups[name] = SpikeGeneratorGroup(count, self.spikegen_index,
185
+ self.spikegen_times, sorted=True)
186
+ self.spike_monitors[name] = SpikeMonitor(self.input_groups[name])
187
+ # Regular neuron model group
188
+ elif self.model_registry[name]['graph_type'] == 'neuron':
189
+ self.neuron_groups[name] = NeuronGroup(count, model=self.model_registry[name]['model_eqs'],
190
+ threshold=self.model_registry[name]['threshold'],
191
+ reset=self.model_registry[name]['reset'],
192
+ method=self.model_registry[name]['method'],
193
+ events=dict(self.model_registry[name]['events']))
194
+ # These "run regularly" methods bypass the standard Brian integration step
195
+ if 'run_regularly' in self.model_registry[name]:
196
+ for program in self.model_registry[name]['run_regularly']:
197
+ self.neuron_groups[name].run_regularly(program['eqs'], when=program['when'])
198
+ # These "run on event" methods trigger when a custom event happens
199
+ if 'run_on_event' in self.model_registry[name]:
200
+ for program in self.model_registry[name]['run_on_event']:
201
+ self.neuron_groups[name].run_on_event(program['event'], program['eqs'])
202
+ # Copy over states
203
+ for state in self.model_registry[name]['state']:
204
+ getattr(self.neuron_groups[name], f"{state}")[:] = self.neuron_states[name][state]
205
+ self.spike_monitors[name] = SpikeMonitor(self.neuron_groups[name])
206
+
207
+ # Create synapse groups
208
+ for (name, source, target) in self.edge_set:
209
+ full_name = f"{name}_{source}_{target}"
210
+ if self.model_registry[source]['graph_type'] == 'input':
211
+ self.synapse_groups[full_name] = Synapses(self.input_groups[source],
212
+ self.neuron_groups[target],
213
+ model=self.model_registry[name]['model_eqs'],
214
+ on_pre=self.model_registry[name]['on_pre'])
215
+ else:
216
+ self.synapse_groups[full_name] = Synapses(self.neuron_groups[source],
217
+ self.neuron_groups[target],
218
+ model=self.model_registry[name]['model_eqs'],
219
+ on_pre=self.model_registry[name]['on_pre'])
220
+ # Copy over connections
221
+ self.synapse_groups[full_name].connect(i=self.synapse_connections[full_name]['i'],
222
+ j=self.synapse_connections[full_name]['j'])
223
+ # Copy over states
224
+ for state in self.model_registry[name]['state']:
225
+ getattr(self.synapse_groups[full_name], f"{state}")[:,:] = self.synapse_states[full_name][state]
226
+
227
+ # Add all the objects to the network
228
+ for value in self.input_groups.values():
229
+ self.brian_net.add(value)
230
+ for value in self.neuron_groups.values():
231
+ self.brian_net.add(value)
232
+ for value in self.synapse_groups.values():
233
+ self.brian_net.add(value)
234
+ for value in self.spike_monitors.values():
235
+ self.brian_net.add(value)
236
+
237
+ # This is the scheduling of events needed for the synapse input not be discarded
238
+ # (the default handling of synapses occurs between thresholds and resets)
239
+ self.brian_net.schedule = ['start', 'groups', 'thresholds', 'resets', 'synapses', 'end']
240
+
241
+ # Collect any output from the simulation
242
+ def read_spikes(self):
243
+ self.spike_list = []
244
+ offset = 0
245
+ for name, monitor in self.spike_monitors.items():
246
+ self.spike_list.extend([[] for _ in range(self.group_count[name])])
247
+ for s in range(len(monitor)):
248
+ self.spike_list[offset+monitor.i[s]].append(monitor.t[s]/ms)
249
+ offset += self.group_count[name]
250
+
251
+ return self.spike_list
252
+
253
+ # Return spikes as event list
254
+ def get_spikes(self):
255
+ if self.spike_list is None:
256
+ return self.read_spikes()
257
+ else:
258
+ return self.spike_list
259
+
260
+ def plot_spikes(self, figsize=(8,6), linelengths=0.8, linewidths=1.0,
261
+ color_dict={'LIF': 'C0', 'IN': 'C1'}, tick_names=False):
262
+ if self.spike_list is None:
263
+ self.read_spikes()
264
+
265
+ # Plot the event list information
266
+ plt.figure(figsize=figsize)
267
+
268
+ # We can also color the rows according to population
269
+ if color_dict is None:
270
+ color_dict = {key: f"C{i%10}" for i, key in enumerate(self.group_count.keys())}
271
+ event_color = []
272
+ for name, count in self.group_count.items():
273
+ if name not in color_dict:
274
+ color_dict[name] = f"C{len(color_dict)%10}"
275
+ event_color.extend([color_dict[name]] * count)
276
+ # colored lines (for legend)
277
+ for key in color_dict.keys():
278
+ plt.plot(0,0,'-',color=color_dict[key],linewidth=2.0)
279
+
280
+ # The spike raster is plotted using eventplot
281
+ plt.eventplot(self.spike_list, colors=event_color, lineoffsets=1,
282
+ linelengths=linelengths, linewidths=linewidths)
283
+
284
+ # Tick names (may be too crowded with many neurons)
285
+ if tick_names:
286
+ plt.yticks(list(self.node_map.values()), list(self.node_map.keys()))
287
+
288
+ plt.title('Spike Raster')
289
+ plt.xlabel('Time (ms)')
290
+ plt.ylabel('Neuron (index)')
291
+ plt.tight_layout()
292
+ plt.legend(color_dict.keys())
@@ -0,0 +1,26 @@
1
+ # Base model registry
2
+ model_registry = {'IN': {'graph_type': 'input', 'state': {}},
3
+ 'LIF': {'graph_type': 'neuron',
4
+ 'model_eqs' : '''v : 1
5
+ v_thresh : 1
6
+ v_reset : 1
7
+ v_bias : 1
8
+ v_leak : 1
9
+ ''',
10
+ 'method' : 'exact',
11
+ 'threshold' : 'v>v_thresh',
12
+ 'reset' : 'v=v_reset',
13
+ 'events' : {},
14
+ 'run_regularly' : [{'eqs': 'v*=(1.0-v_leak)', 'when': 'resets'},
15
+ {'eqs': 'v+=v_bias', 'when': 'groups'}],
16
+ 'state': {'v': {'dsl': 'voltage', 'default': 0.0},
17
+ 'v_thresh': {'dsl': 'threshold', 'default': 1.0},
18
+ 'v_reset': {'dsl': 'reset', 'default': 0.0},
19
+ 'v_bias': {'dsl': 'bias', 'default': 0.0},
20
+ 'v_leak': {'dsl': 'leak', 'default': 1.0}}},
21
+ 'PSP': {'graph_type': 'synapse',
22
+ 'model_eqs' : 'weight : 1',
23
+ 'on_pre' : 'v+=weight',
24
+ 'state': {'delay': {'dsl': 'delay', 'default': 1.0, 'unit': 'ms'},
25
+ 'weight': {'dsl': 'weight', 'default': 1.0}}}
26
+ }
@@ -0,0 +1,23 @@
1
+ # Model registry
2
+ model_registry = {'pLIF': {'graph_type': 'neuron',
3
+ 'model_eqs' : '''v : 1
4
+ v_thresh : 1
5
+ v_reset : 1
6
+ v_bias : 1
7
+ v_leak : 1
8
+ p_spike : 1
9
+ ''',
10
+ 'method' : 'exact',
11
+ 'threshold' : '(v>v_thresh) and (rand()<=p_spike)',
12
+ 'reset' : '', # probabilistic spiking requires custom event
13
+ 'events' : {'pass_thresh': 'v>v_thresh'},
14
+ 'run_regularly' : [{'eqs': 'v*=(1.0-v_leak)', 'when': 'resets'},
15
+ {'eqs': 'v+=v_bias', 'when': 'groups'}],
16
+ 'run_on_event' : [{'event': 'pass_thresh', 'eqs': 'v=v_reset'}],
17
+ 'state': {'v': {'dsl': 'voltage', 'default': 0.0},
18
+ 'v_thresh': {'dsl': 'threshold', 'default': 1.0},
19
+ 'v_reset': {'dsl': 'reset', 'default': 0.0},
20
+ 'v_bias': {'dsl': 'bias', 'default': 0.0},
21
+ 'v_leak': {'dsl': 'leak', 'default': 1.0},
22
+ 'p_spike': {'dsl': 'prob', 'default': 1.0}}}
23
+ }
File without changes
@@ -0,0 +1,267 @@
1
+ import os
2
+ import sys
3
+ from pathlib import Path
4
+ import importlib.util
5
+
6
+ try:
7
+ import fugu
8
+ except ImportError:
9
+ fugu = None
10
+ if fugu is not None:
11
+ from fugu import Scaffold
12
+ from fugu.scaffold import PortData, ChannelData
13
+ from fugu.backends import snn_Backend
14
+
15
+ import time
16
+ from collections import Counter, defaultdict
17
+
18
+ import numpy as np
19
+ import networkx as nx
20
+ import matplotlib.pyplot as plt
21
+
22
+ class SimFugu:
23
+ def __init__(self, dsl_net):
24
+ if fugu is None:
25
+ raise ImportError("fugu package is required for SimFugu")
26
+
27
+ self.dsl_net = dsl_net
28
+ self.timesteps = None
29
+
30
+ self.node_map = None
31
+ self.spike_list = None
32
+
33
+ self.model_registry = self.import_registry()
34
+
35
+ self.fugu_backend = None
36
+ self.debug = None
37
+ self.backend_args = None
38
+ self.scaffold = None
39
+
40
+ # Dynamically import the model registry files
41
+ def import_registry(self):
42
+ registry = dict()
43
+ registry_dir = Path(__file__).resolve().parent / 'registry'
44
+ sys.path.insert(0, str(registry_dir.parent))
45
+ for file_path in registry_dir.glob("*.py"):
46
+ if file_path.name == "__init__.py":
47
+ continue
48
+ module_name = f"registry.{file_path.stem}"
49
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
50
+ module = importlib.util.module_from_spec(spec)
51
+ spec.loader.exec_module(module)
52
+ registry.update(getattr(module, 'model_registry', {}))
53
+ sys.path.pop(0)
54
+ return registry
55
+
56
+ # Convert between dsl model to Fugu model
57
+ def rekey_model(self, data):
58
+ for key, value in self.model_registry[data['model']]['param'].items():
59
+ if value['dsl'] is not None:
60
+ if value['dsl'] == 'delay':
61
+ data[key] = int(data.pop(value['dsl']))
62
+ else:
63
+ data[key] = data.pop(value['dsl'])
64
+ else:
65
+ data[key] = value['default']
66
+ return data
67
+
68
+ def compile(self, backend='snn', backend_args={'record': 'all'}, debug=False):
69
+ self.debug = debug
70
+ if backend == 'snn':
71
+ self.fugu_backend = snn_Backend()
72
+ else:
73
+ raise NotImplementedError(f"Backend '{backend}' is not supported.")
74
+ self.backend_args = backend_args
75
+ if self.debug:
76
+ self.backend_args['debug_mode'] = True
77
+
78
+ # Convert network to Fugu
79
+ start_time = time.perf_counter()
80
+ self.to_fugu()
81
+ end_time = time.perf_counter()
82
+ self.compile_time = end_time - start_time
83
+ if self.debug:
84
+ print(f"Compile time: {self.compile_time}")
85
+
86
+ # Run the network
87
+ def run(self, timesteps=10.0, verbose=False):
88
+ self.timesteps = int(timesteps)
89
+ self.verbose = verbose
90
+
91
+ # Run network
92
+ start_time = time.perf_counter()
93
+ self.fugu_result = self.fugu_backend.run(n_steps=timesteps)
94
+ end_time = time.perf_counter()
95
+ self.run_time = end_time - start_time
96
+ if self.verbose:
97
+ print(f"Run time: {self.run_time}")
98
+
99
+ # Translates a Sango network into a Fugu Scaffold
100
+ def to_fugu(self):
101
+ # Get a flattened graph object
102
+ self.dsl_graph = self.dsl_net._topology.to_nx()
103
+
104
+ # Convert networkx to scaffold and graph
105
+ self.scaffold = self.to_scaffold(self.dsl_graph)
106
+
107
+ # Compile scaffold in Fugu backend
108
+ self.fugu_backend.compile(self.scaffold, self.backend_args)
109
+
110
+ # Dummy brick object for Fugu scaffold
111
+ class DummyBrick():
112
+ def __init__(self, name='dummy'):
113
+ self.name = name
114
+ self.vector = []
115
+ self.index = 0
116
+ self.is_built = True
117
+ # This object is mainly used for managing Fugu inputs
118
+ def __iter__(self):
119
+ self.index = 0
120
+ return iter(self.vector)
121
+ def __next__(self):
122
+ if self.index < len(self.data):
123
+ result = self.vector[self.index]
124
+ self.index += 1
125
+ return result
126
+ else:
127
+ raise StopIteration
128
+
129
+ # Convert Sango network to Fugu Scaffold
130
+ def to_scaffold(self, net):
131
+ # Set up a dummy scaffold in Fugu
132
+ scaffold = Scaffold()
133
+ scaffold_circuit = nx.DiGraph()
134
+ # Input and main Sango bricks
135
+ input_brick = {'tag': 'input',
136
+ 'name': 'InputBrick',
137
+ 'brick': self.DummyBrick('input'),
138
+ 'layer': 'input',
139
+ 'ports': {'output': None},
140
+ 'is_built': True}
141
+ sango_brick = {'tag': 'sango',
142
+ 'name': 'SangoBrick',
143
+ 'brick': self.DummyBrick('sango'),
144
+ 'layer': 'output',
145
+ 'ports': {'input': None},
146
+ 'is_built': True}
147
+ # Connect dummy circuit
148
+ scaffold_circuit.add_node(0, **input_brick)
149
+ scaffold_circuit.add_node(1, **sango_brick)
150
+ scaffold_circuit.add_edge(0, 1, bind={'input': 'output'})
151
+ scaffold.tag_to_name = {'input': 'InputBrick', 'sango': 'SangoBrick'}
152
+ scaffold.brick_to_number = {'InputBrick': 0, 'SangoBrick': 1}
153
+
154
+ # Set up underlying graph
155
+ scaffold_graph = nx.DiGraph()
156
+ self.num_nodes = self.dsl_graph.number_of_nodes()
157
+ self.node_data = [dict() for _ in range(self.num_nodes)]
158
+ self.node_index = dict()
159
+ self.group_count = Counter()
160
+ self.local_index = [0 for _ in range(self.num_nodes)]
161
+ self.group_count['IN'] = 0
162
+ self.spike_input = dict()
163
+
164
+ # Global node data
165
+ for n, (node, data) in enumerate(self.dsl_graph.nodes(data=True)):
166
+ self.node_index[node] = n
167
+ self.node_data[n] = self.rekey_model(data)
168
+ self.group_count.update([self.node_data[n]['model']])
169
+ self.local_index[n] = self.group_count[self.node_data[n]['model']] - 1
170
+ # Add spike times for inputs
171
+ if self.model_registry[self.node_data[n]['model']]['node_class'] == 'InputNeuron':
172
+ self.spike_input[node]=data['times']
173
+
174
+ # Remove the input model (IN) if counts are zero
175
+ if self.group_count['IN'] == 0:
176
+ del self.group_count['IN']
177
+
178
+ # Get the model insertion order of our counter, and organize node map by group
179
+ self.group_index = {key: index for index, key in enumerate(self.group_count.keys())}
180
+ self.group_sorted_count = [self.group_count[group] for group in self.group_index]
181
+ self.group_offset = [0] + [sum(self.group_sorted_count[:i+1]) for i in range(len(self.group_sorted_count))]
182
+ self.node_map = {key: self.group_offset[self.group_index[self.node_data[n]['model']]] + self.local_index[n]
183
+ for key, n in self.node_index.items()}
184
+
185
+ # Attach spike times to input brick
186
+ spike_times = defaultdict(list)
187
+ for n, (neuron, times) in enumerate(self.spike_input.items()):
188
+ for t in times:
189
+ spike_times[t].append(neuron)
190
+ spike_vector = [spike_times[key] for key in range(max(spike_times.keys())+1)]
191
+ scaffold_circuit.nodes[0]['brick'].vector = spike_vector
192
+ scaffold_circuit.nodes[0]['ports']['output'] = PortData(spec=None,
193
+ channels = {'data': ChannelData(spec=None, neurons = list(self.spike_input.keys()))})
194
+ scaffold_circuit.nodes[1]['ports']['input'] = PortData(spec=None,
195
+ channels = {'data': ChannelData(spec=None, neurons = [])})
196
+ scaffold.circuit = scaffold_circuit
197
+
198
+ # Insert nodes by group
199
+ for n, (node, nidx) in enumerate(self.node_map.items()):
200
+ node_data = self.node_data[n]
201
+ if node_data['model'] == 'IN':
202
+ scaffold_graph.add_node(node, neuron_number=nidx,
203
+ brick='InputBrick', **node_data)
204
+ else:
205
+ scaffold_graph.add_node(node, neuron_number=nidx,
206
+ brick='SangoBrick', **node_data)
207
+
208
+ # Global edge data
209
+ for source, target, data in self.dsl_graph.edges(data=True):
210
+ edge_data = self.rekey_model(data)
211
+ scaffold_graph.add_edge(source, target, **edge_data)
212
+
213
+ # Attach the built graph to the scaffold and mark as built
214
+ scaffold.graph = scaffold_graph
215
+ scaffold.is_built = True
216
+
217
+ return scaffold
218
+
219
+ # Collect any output from the simulation
220
+ def read_spikes(self):
221
+ # Reading in Fugu's dataframe results
222
+ spike_dict = defaultdict(list)
223
+ for spike in self.fugu_result.itertuples(index=False):
224
+ spike_dict[int(spike.neuron_number)].append(spike.time)
225
+ self.spike_list = [spike_dict[key] for key in range(len(self.node_map))]
226
+ return self.spike_list
227
+
228
+ # Return spikes as event list
229
+ def get_spikes(self):
230
+ if self.spike_list is None:
231
+ return self.read_spikes()
232
+ else:
233
+ return self.spike_list
234
+
235
+ def plot_spikes(self, figsize=(8,6), linelengths=0.8, linewidths=1.0,
236
+ color_dict={'LIF': 'C0', 'IN': 'C1'}, tick_names=False):
237
+ if self.spike_list is None:
238
+ self.read_spikes()
239
+
240
+ # Plot the event list information
241
+ plt.figure(figsize=figsize)
242
+
243
+ # We can also color the rows according to population
244
+ if color_dict is None:
245
+ color_dict = {key: f"C{i%10}" for i, key in enumerate(self.group_count.keys())}
246
+ event_color = []
247
+ for name, count in self.group_count.items():
248
+ if name not in color_dict:
249
+ color_dict[name] = f"C{len(color_dict)%10}"
250
+ event_color.extend([color_dict[name]] * count)
251
+ # colored lines (for legend)
252
+ for key in color_dict.keys():
253
+ plt.plot(0,0,'-',color=color_dict[key],linewidth=2.0)
254
+
255
+ # The spike raster is plotted using eventplot
256
+ plt.eventplot(self.spike_list, colors=event_color, lineoffsets=1,
257
+ linelengths=linelengths, linewidths=linewidths)
258
+
259
+ # Tick names (may be too crowded with many neurons)
260
+ if tick_names:
261
+ plt.yticks(list(self.node_map.values()), list(self.node_map.keys()))
262
+
263
+ plt.title('Spike Raster')
264
+ plt.xlabel('Time (ms)')
265
+ plt.ylabel('Neuron (index)')
266
+ plt.tight_layout()
267
+ plt.legend(color_dict.keys())
@@ -0,0 +1,15 @@
1
+ # Base model registry
2
+ model_registry = {
3
+ 'LIF': {'node_class': 'LIFNeuron',
4
+ 'param': {'threshold': {'dsl': 'threshold', 'default': 0.0},
5
+ 'reset_voltage': {'dsl': 'reset', 'default': 0.0},
6
+ 'decay': {'dsl': 'leak', 'default': 1.0},
7
+ 'voltage': {'dsl': 'voltage', 'default': 0.0},
8
+ 'bias': {'dsl': 'bias', 'default': 0.0}}},
9
+ 'IN': {'node_class': 'InputNeuron',
10
+ 'param': {'threshold': {'dsl': None, 'default': 0.1},
11
+ 'voltage': {'dsl': None, 'default': 0.0}}},
12
+ 'PSP': {'edge_class': 'Synapse',
13
+ 'param': {'weight': {'dsl': 'weight', 'default': 1.0},
14
+ 'delay': {'dsl': 'delay', 'default': 1.0}}}
15
+ }
@@ -0,0 +1,10 @@
1
+ # Prob model registry
2
+ model_registry = {
3
+ 'pLIF': {'node_class': 'LIFNeuron',
4
+ 'param': {'threshold': {'dsl': 'threshold', 'default': 0.0},
5
+ 'reset_voltage': {'dsl': 'reset', 'default': 0.0},
6
+ 'decay': {'dsl': 'leak', 'default': 1.0},
7
+ 'voltage': {'dsl': 'voltage', 'default': 0.0},
8
+ 'p': {'dsl': 'prob', 'default': 1.0},
9
+ 'bias': {'dsl': 'bias', 'default': 0.0}}}
10
+ }
File without changes