def __init__(self, synapse_type, ring_buffer_sigma, spikes_per_second, config, population_table_type=None, synapse_io=None): self._synapse_type = synapse_type self._ring_buffer_sigma = ring_buffer_sigma self._spikes_per_second = spikes_per_second # Get the type of population table self._poptable_type = population_table_type if population_table_type is None: population_table_type = ("MasterPopTableAs" + config.get("MasterPopTable", "generator")) algorithms = get_valid_components(master_pop_table_generators, "master_pop_table_as") self._poptable_type = algorithms[population_table_type]() # Get the synapse IO self._synapse_io = synapse_io if synapse_io is None: self._synapse_io = SynapseIORowBased() if self._ring_buffer_sigma is None: self._ring_buffer_sigma = config.getfloat("Simulation", "ring_buffer_sigma") if self._spikes_per_second is None: self._spikes_per_second = config.getfloat("Simulation", "spikes_per_second") # Prepare for dealing with STDP - there can only be one (non-static) # synapse dynamics per vertex at present self._synapse_dynamics = SynapseDynamicsStatic() # Keep the details once computed to allow reading back self._weight_scales = dict() self._delay_key_index = dict() self._retrieved_blocks = dict() # A list of connection holders to be filled in pre-run, indexed by # the edge the connection is for self._pre_run_connection_holders = defaultdict(list) # Limit the DTCM used by one-to-one connections self._one_to_one_connection_dtcm_max_bytes = config.getint( "Simulation", "one_to_one_connection_dtcm_max_bytes") # TODO: Hard-coded to 0 to disable as currently broken! self._one_to_one_connection_dtcm_max_bytes = 0
from spynnaker.pyNN.models.neuron.plasticity.stdp.weight_dependence \ import WeightDependenceAdditive from spynnaker.pyNN.models.neuron.plasticity.stdp.timing_dependence \ import TimingDependenceSpikePair from spynnaker.pyNN.models.neuron.synapse_dynamics import SynapseDynamicsSTDP from spynnaker.pyNN.models.neural_projections.synapse_information \ import SynapseInformation import pytest @pytest.mark.parametrize( "dynamics,size,exception,max_size", [ # Normal static rows can be up to 255 words, 1 word per synapse (SynapseDynamicsStatic(), 512, SynapseRowTooBigException, 255), # Normal static row of 20 is allowed - 20 words = 80 bytes (SynapseDynamicsStatic(), 20, None, 80), # STDP row with spike pair rule is 1 words per synapse but extra # header takes some of the space, so only 252 synapses allowed (SynapseDynamicsSTDP( TimingDependenceSpikePair(), WeightDependenceAdditive()), 512, SynapseRowTooBigException, 252), # STDP row with spike pair rule of 20 is allowed - 20 words = 80 bytes (SynapseDynamicsSTDP(TimingDependenceSpikePair(), WeightDependenceAdditive()), 20, None, 80) ]) def test_get_max_row_bytes(dynamics, size, exception, max_size):
def __init__(self, *args, **kwargs): CommonSynapseDynamicsStatic.__init__(self)
class SynapticManager(object): """ Deals with synapses """ # pylint: disable=too-many-arguments, too-many-locals __slots__ = [ "_delay_key_index", "_one_to_one_connection_dtcm_max_bytes", "_poptable_type", "_pre_run_connection_holders", "_retrieved_blocks", "_ring_buffer_sigma", "_spikes_per_second", "_synapse_dynamics", "_synapse_io", "_synapse_type", "_weight_scales" ] def __init__(self, synapse_type, ring_buffer_sigma, spikes_per_second, config, population_table_type=None, synapse_io=None): self._synapse_type = synapse_type self._ring_buffer_sigma = ring_buffer_sigma self._spikes_per_second = spikes_per_second # Get the type of population table self._poptable_type = population_table_type if population_table_type is None: population_table_type = ("MasterPopTableAs" + config.get("MasterPopTable", "generator")) algorithms = get_valid_components(master_pop_table_generators, "master_pop_table_as") self._poptable_type = algorithms[population_table_type]() # Get the synapse IO self._synapse_io = synapse_io if synapse_io is None: self._synapse_io = SynapseIORowBased() if self._ring_buffer_sigma is None: self._ring_buffer_sigma = config.getfloat("Simulation", "ring_buffer_sigma") if self._spikes_per_second is None: self._spikes_per_second = config.getfloat("Simulation", "spikes_per_second") # Prepare for dealing with STDP - there can only be one (non-static) # synapse dynamics per vertex at present self._synapse_dynamics = SynapseDynamicsStatic() # Keep the details once computed to allow reading back self._weight_scales = dict() self._delay_key_index = dict() self._retrieved_blocks = dict() # A list of connection holders to be filled in pre-run, indexed by # the edge the connection is for self._pre_run_connection_holders = defaultdict(list) # Limit the DTCM used by one-to-one connections self._one_to_one_connection_dtcm_max_bytes = config.getint( "Simulation", "one_to_one_connection_dtcm_max_bytes") # TODO: Hard-coded to 0 to disable as currently broken! self._one_to_one_connection_dtcm_max_bytes = 0 @property def synapse_dynamics(self): return self._synapse_dynamics @synapse_dynamics.setter def synapse_dynamics(self, synapse_dynamics): # We can always override static dynamics or None if isinstance(self._synapse_dynamics, SynapseDynamicsStatic): self._synapse_dynamics = synapse_dynamics # We can ignore a static dynamics trying to overwrite a plastic one elif isinstance(synapse_dynamics, SynapseDynamicsStatic): pass # Otherwise, the dynamics must be equal elif not synapse_dynamics.is_same_as(self._synapse_dynamics): raise SynapticConfigurationException( "Synapse dynamics must match exactly when using multiple edges" "to the same population") @property def synapse_type(self): return self._synapse_type @property def ring_buffer_sigma(self): return self._ring_buffer_sigma @ring_buffer_sigma.setter def ring_buffer_sigma(self, ring_buffer_sigma): self._ring_buffer_sigma = ring_buffer_sigma @property def spikes_per_second(self): return self._spikes_per_second @spikes_per_second.setter def spikes_per_second(self, spikes_per_second): self._spikes_per_second = spikes_per_second def get_maximum_delay_supported_in_ms(self, machine_time_step): return self._synapse_io.get_maximum_delay_supported_in_ms( machine_time_step) @property def vertex_executable_suffix(self): return self._synapse_dynamics.get_vertex_executable_suffix() def add_pre_run_connection_holder(self, connection_holder, edge, synapse_info): self._pre_run_connection_holders[edge, synapse_info].append( connection_holder) def get_n_cpu_cycles(self): # TODO: Calculate this correctly return 0 def get_dtcm_usage_in_bytes(self): # TODO: Calculate this correctly return 0 def _get_synapse_params_size(self, vertex_slice): per_neuron_usage = ( self._synapse_type.get_sdram_usage_per_neuron_in_bytes()) return (_SYNAPSES_BASE_SDRAM_USAGE_IN_BYTES + (per_neuron_usage * vertex_slice.n_atoms) + (4 * self._synapse_type.get_n_synapse_types())) def _get_static_synaptic_matrix_sdram_requirements(self): # 4 for address of direct addresses, and # 4 for the size of the direct addresses matrix in bytes return 8 def _get_exact_synaptic_blocks_size(self, post_slices, post_slice_index, post_vertex_slice, graph_mapper, in_edges, machine_time_step): """ Get the exact size all of the synaptic blocks """ memory_size = self._get_static_synaptic_matrix_sdram_requirements() # Go through the edges and add up the memory for machine_edge in in_edges: application_edge = graph_mapper.get_application_edge(machine_edge) if isinstance(application_edge, ProjectionApplicationEdge): # Add on the size of the tables to be generated pre_vertex_slice = graph_mapper.get_slice( machine_edge.pre_vertex) pre_slices = \ graph_mapper.get_slices(application_edge.pre_vertex) pre_slice_index = graph_mapper.get_machine_vertex_index( machine_edge.pre_vertex) memory_size += self._get_size_of_synapse_information( application_edge.synapse_information, pre_slices, pre_slice_index, post_slices, post_slice_index, pre_vertex_slice, post_vertex_slice, application_edge.n_delay_stages, machine_time_step, machine_edge) return memory_size def _get_estimate_synaptic_blocks_size(self, post_vertex_slice, in_edges, machine_time_step): """ Get an estimate of the synaptic blocks memory size """ memory_size = self._get_static_synaptic_matrix_sdram_requirements() for in_edge in in_edges: if isinstance(in_edge, ProjectionApplicationEdge): # Get an estimate of the number of post vertices by # assuming that all of them are the same size as this one post_slices = [ Slice( lo_atom, min(in_edge.post_vertex.n_atoms, lo_atom + post_vertex_slice.n_atoms - 1)) for lo_atom in range(0, in_edge.post_vertex.n_atoms, post_vertex_slice.n_atoms) ] post_slice_index = int( math.floor( float(post_vertex_slice.lo_atom) / float(post_vertex_slice.n_atoms))) # Get an estimate of the number of pre-vertices - clearly # this will not be correct if the SDRAM usage is high! n_atoms_per_machine_vertex = sys.maxsize if isinstance(in_edge.pre_vertex, AbstractHasGlobalMaxAtoms): n_atoms_per_machine_vertex = \ in_edge.pre_vertex.get_max_atoms_per_core() if in_edge.pre_vertex.n_atoms < n_atoms_per_machine_vertex: n_atoms_per_machine_vertex = in_edge.pre_vertex.n_atoms pre_slices = [Slice(0, in_edge.pre_vertex.n_atoms - 1)] pre_slice_index = 0 memory_size += self._get_size_of_synapse_information( in_edge.synapse_information, pre_slices, pre_slice_index, post_slices, post_slice_index, pre_slices[pre_slice_index], post_vertex_slice, in_edge.n_delay_stages, machine_time_step, in_edge) return memory_size * _SYNAPSE_SDRAM_OVERSCALE def _get_size_of_synapse_information(self, synapse_information, pre_slices, pre_slice_index, post_slices, post_slice_index, pre_vertex_slice, post_vertex_slice, n_delay_stages, machine_time_step, in_edge): memory_size = 0 for synapse_info in synapse_information: undelayed_size, delayed_size = \ self._synapse_io.get_sdram_usage_in_bytes( synapse_info, pre_slices, pre_slice_index, post_slices, post_slice_index, pre_vertex_slice, post_vertex_slice, n_delay_stages, self._poptable_type, machine_time_step, in_edge) memory_size = self._poptable_type.get_next_allowed_address( memory_size) memory_size += undelayed_size memory_size = self._poptable_type.get_next_allowed_address( memory_size) memory_size += delayed_size return memory_size def _get_synapse_dynamics_parameter_size(self, vertex_slice, in_edges=None): """ Get the size of the synapse dynamics region """ # Does the size of the parameters area depend on presynaptic # connections in any way? if isinstance(self.synapse_dynamics, AbstractSynapseDynamicsStructural): return self._synapse_dynamics.get_parameters_sdram_usage_in_bytes( vertex_slice.n_atoms, self._synapse_type.get_n_synapse_types(), in_edges=in_edges) else: return self._synapse_dynamics.get_parameters_sdram_usage_in_bytes( vertex_slice.n_atoms, self._synapse_type.get_n_synapse_types()) def get_sdram_usage_in_bytes(self, vertex_slice, in_edges, machine_time_step): return (self._get_synapse_params_size(vertex_slice) + self._get_synapse_dynamics_parameter_size(vertex_slice, in_edges=in_edges) + self._get_estimate_synaptic_blocks_size( vertex_slice, in_edges, machine_time_step) + self._poptable_type.get_master_population_table_size( vertex_slice, in_edges)) def _reserve_memory_regions(self, spec, machine_vertex, vertex_slice, machine_graph, all_syn_block_sz, graph_mapper): spec.reserve_memory_region( region=POPULATION_BASED_REGIONS.SYNAPSE_PARAMS.value, size=self._get_synapse_params_size(vertex_slice), label='SynapseParams') master_pop_table_sz = \ self._poptable_type.get_exact_master_population_table_size( machine_vertex, machine_graph, graph_mapper) if master_pop_table_sz > 0: spec.reserve_memory_region( region=POPULATION_BASED_REGIONS.POPULATION_TABLE.value, size=master_pop_table_sz, label='PopTable') if all_syn_block_sz > 0: spec.reserve_memory_region( region=POPULATION_BASED_REGIONS.SYNAPTIC_MATRIX.value, size=all_syn_block_sz, label='SynBlocks') synapse_dynamics_sz = \ self._get_synapse_dynamics_parameter_size( vertex_slice, machine_graph.get_edges_ending_at_vertex(machine_vertex)) if synapse_dynamics_sz != 0: spec.reserve_memory_region( region=POPULATION_BASED_REGIONS.SYNAPSE_DYNAMICS.value, size=synapse_dynamics_sz, label='synapseDynamicsParams') def get_number_of_mallocs_used_by_dsg(self): return 4 @staticmethod def _ring_buffer_expected_upper_bound(weight_mean, weight_std_dev, spikes_per_second, machine_timestep, n_synapses_in, sigma): """ Provides expected upper bound on accumulated values in a ring\ buffer element. Requires an assessment of maximum Poisson input rate. Assumes knowledge of mean and SD of weight distribution, fan-in\ and timestep. All arguments should be assumed real values except n_synapses_in\ which will be an integer. :param weight_mean: Mean of weight distribution (in either nA or\ microSiemens as required) :param weight_std_dev: SD of weight distribution :param spikes_per_second: Maximum expected Poisson rate in Hz :param machine_timestep: in us :param n_synapses_in: No of connected synapses :param sigma: How many SD above the mean to go for upper bound; a\ good starting choice is 5.0. Given length of simulation we can\ set this for approximate number of saturation events. """ # E[ number of spikes ] in a timestep steps_per_second = 1000000.0 / machine_timestep average_spikes_per_timestep = ( float(n_synapses_in * spikes_per_second) / steps_per_second) # Exact variance contribution from inherent Poisson variation poisson_variance = average_spikes_per_timestep * (weight_mean**2) # Upper end of range for Poisson summation required below # upper_bound needs to be an integer upper_bound = int( round(average_spikes_per_timestep + POSSION_SIGMA_SUMMATION_LIMIT * math.sqrt(average_spikes_per_timestep))) # Closed-form exact solution for summation that gives the variance # contributed by weight distribution variation when modulated by # Poisson PDF. Requires scipy.special for gamma and incomplete gamma # functions. Beware: incomplete gamma doesn't work the same as # Mathematica because (1) it's regularised and needs a further # multiplication and (2) it's actually the complement that is needed # i.e. 'gammaincc'] weight_variance = 0.0 if weight_std_dev > 0: lngamma = special.gammaln(1 + upper_bound) gammai = special.gammaincc(1 + upper_bound, average_spikes_per_timestep) big_ratio = (math.log(average_spikes_per_timestep) * upper_bound - lngamma) if -701.0 < big_ratio < 701.0 and big_ratio != 0.0: log_weight_variance = ( -average_spikes_per_timestep + math.log(average_spikes_per_timestep) + 2.0 * math.log(weight_std_dev) + math.log( math.exp(average_spikes_per_timestep) * gammai - math.exp(big_ratio))) weight_variance = math.exp(log_weight_variance) # upper bound calculation -> mean + n * SD return ((average_spikes_per_timestep * weight_mean) + (sigma * math.sqrt(poisson_variance + weight_variance))) def _get_ring_buffer_to_input_left_shifts(self, machine_vertex, machine_graph, graph_mapper, post_slices, post_slice_index, post_vertex_slice, machine_timestep, weight_scale): """ Get the scaling of the ring buffer to provide as much accuracy as\ possible without too much overflow """ weight_scale_squared = weight_scale * weight_scale n_synapse_types = self._synapse_type.get_n_synapse_types() running_totals = [RunningStats() for _ in range(n_synapse_types)] delay_running_totals = [RunningStats() for _ in range(n_synapse_types)] total_weights = numpy.zeros(n_synapse_types) biggest_weight = numpy.zeros(n_synapse_types) weights_signed = False rate_stats = [RunningStats() for _ in range(n_synapse_types)] steps_per_second = 1000000.0 / machine_timestep for m_edge in machine_graph.get_edges_ending_at_vertex(machine_vertex): pre_vertex_slice = graph_mapper.get_slice(m_edge.pre_vertex) app_edge = graph_mapper.get_application_edge(m_edge) pre_slices = [ graph_mapper.get_slice(internal_machine_vertex) for internal_machine_vertex in graph_mapper.get_machine_vertices(app_edge.pre_vertex) ] pre_slice_index = pre_slices.index(pre_vertex_slice) if isinstance(app_edge, ProjectionApplicationEdge): for synapse_info in app_edge.synapse_information: synapse_type = synapse_info.synapse_type synapse_dynamics = synapse_info.synapse_dynamics connector = synapse_info.connector weight_mean = abs( synapse_dynamics.get_weight_mean( connector, pre_slices, pre_slice_index, post_slices, post_slice_index, pre_vertex_slice, post_vertex_slice) * weight_scale) n_connections = \ connector.get_n_connections_to_post_vertex_maximum( pre_slices, pre_slice_index, post_slices, post_slice_index, pre_vertex_slice, post_vertex_slice) weight_variance = abs( synapse_dynamics.get_weight_variance( connector, pre_slices, pre_slice_index, post_slices, post_slice_index, pre_vertex_slice, post_vertex_slice) * weight_scale_squared) running_totals[synapse_type].add_items( weight_mean, weight_variance, n_connections) delay_variance = synapse_dynamics.get_delay_variance( connector, pre_slices, pre_slice_index, post_slices, post_slice_index, pre_vertex_slice, post_vertex_slice) delay_running_totals[synapse_type].add_items( 0.0, delay_variance, n_connections) weight_max = (synapse_dynamics.get_weight_maximum( connector, pre_slices, pre_slice_index, post_slices, post_slice_index, pre_vertex_slice, post_vertex_slice) * weight_scale) biggest_weight[synapse_type] = max( biggest_weight[synapse_type], weight_max) spikes_per_tick = max( 1.0, self._spikes_per_second / steps_per_second) spikes_per_second = self._spikes_per_second if isinstance(app_edge.pre_vertex, SpikeSourcePoisson): spikes_per_second = app_edge.pre_vertex.rate if hasattr(spikes_per_second, "__getitem__"): spikes_per_second = max(spikes_per_second) elif get_simulator().is_a_pynn_random( spikes_per_second): spikes_per_second = get_maximum_probable_value( spikes_per_second, pre_vertex_slice.n_atoms) prob = 1.0 - ((1.0 / 100.0) / pre_vertex_slice.n_atoms) spikes_per_tick = spikes_per_second / steps_per_second spikes_per_tick = scipy.stats.poisson.ppf( prob, spikes_per_tick) rate_stats[synapse_type].add_items(spikes_per_second, 0, n_connections) total_weights[synapse_type] += spikes_per_tick * ( weight_max * n_connections) if synapse_dynamics.are_weights_signed(): weights_signed = True max_weights = numpy.zeros(n_synapse_types) for synapse_type in range(n_synapse_types): stats = running_totals[synapse_type] rates = rate_stats[synapse_type] if delay_running_totals[synapse_type].variance == 0.0: max_weights[synapse_type] = max(total_weights[synapse_type], biggest_weight[synapse_type]) else: max_weights[synapse_type] = min( self._ring_buffer_expected_upper_bound( stats.mean, stats.standard_deviation, rates.mean, machine_timestep, stats.n_items, self._ring_buffer_sigma), total_weights[synapse_type]) max_weights[synapse_type] = max(max_weights[synapse_type], biggest_weight[synapse_type]) # Convert these to powers max_weight_powers = (0 if w <= 0 else int( math.ceil(max(0, math.log(w, 2)))) for w in max_weights) # If 2^max_weight_power equals the max weight, we have to add another # power, as range is 0 - (just under 2^max_weight_power)! max_weight_powers = (w + 1 if (2**w) <= a else w for w, a in zip(max_weight_powers, max_weights)) # If we have synapse dynamics that uses signed weights, # Add another bit of shift to prevent overflows if weights_signed: max_weight_powers = (m + 1 for m in max_weight_powers) return list(max_weight_powers) @staticmethod def _get_weight_scale(ring_buffer_to_input_left_shift): """ Return the amount to scale the weights by to convert them from \ floating point values to 16-bit fixed point numbers which can be \ shifted left by ring_buffer_to_input_left_shift to produce an\ s1615 fixed point number """ return float(math.pow(2, 16 - (ring_buffer_to_input_left_shift + 1))) def _write_synapse_parameters(self, spec, machine_vertex, machine_graph, graph_mapper, post_slices, post_slice_index, post_vertex_slice, input_type, machine_time_step): # Get the ring buffer shifts and scaling factors weight_scale = input_type.get_global_weight_scale() ring_buffer_shifts = self._get_ring_buffer_to_input_left_shifts( machine_vertex, machine_graph, graph_mapper, post_slices, post_slice_index, post_vertex_slice, machine_time_step, weight_scale) spec.switch_write_focus(POPULATION_BASED_REGIONS.SYNAPSE_PARAMS.value) write_parameters_per_neuron( spec, post_vertex_slice, self._synapse_type.get_synapse_type_parameters()) spec.write_array(ring_buffer_shifts) weight_scales = numpy.array([ self._get_weight_scale(r) * weight_scale for r in ring_buffer_shifts ]) return weight_scales def _write_padding(self, spec, synaptic_matrix_region, next_block_start_address): next_block_allowed_address = self._poptable_type\ .get_next_allowed_address(next_block_start_address) if next_block_allowed_address != next_block_start_address: # Pad out data file with the added alignment bytes: spec.comment("\nWriting population table required padding\n") spec.switch_write_focus(synaptic_matrix_region) spec.set_register_value(register_id=15, data=next_block_allowed_address - next_block_start_address) spec.write_repeat_value(data=0xDD, repeats=15, repeats_is_register=True, data_type=DataType.UINT8) return next_block_allowed_address return next_block_start_address def _write_synaptic_matrix_and_master_population_table( self, spec, post_slices, post_slice_index, machine_vertex, post_vertex_slice, all_syn_block_sz, weight_scales, master_pop_table_region, synaptic_matrix_region, routing_info, graph_mapper, machine_graph, machine_time_step): """ Simultaneously generates both the master population table and the synaptic matrix. """ spec.comment( "\nWriting Synaptic Matrix and Master Population Table:\n") # Track writes inside the synaptic matrix region: block_addr = 0 n_synapse_types = self._synapse_type.get_n_synapse_types() # Get the edges in_edges = machine_graph.get_edges_ending_at_vertex(machine_vertex) # Set up the master population table self._poptable_type.initialise_table(spec, master_pop_table_region) # Set up for single synapses - write the offset of the single synapses # initially 0 single_synapses = list() spec.switch_write_focus(synaptic_matrix_region) spec.write_value(0) single_addr = 0 # For each machine edge in the vertex, create a synaptic list for machine_edge in in_edges: app_edge = graph_mapper.get_application_edge(machine_edge) if isinstance(app_edge, ProjectionApplicationEdge): spec.comment("\nWriting matrix for m_edge:{}\n".format( machine_edge.label)) pre_vertex_slice = graph_mapper.get_slice( machine_edge.pre_vertex) pre_slices = graph_mapper.get_slices(app_edge.pre_vertex) pre_slice_idx = graph_mapper.get_machine_vertex_index( machine_edge.pre_vertex) for synapse_info in app_edge.synapse_information: block_addr, single_addr = self.__write_row( spec, synaptic_matrix_region, synapse_info, pre_slices, pre_slice_idx, post_slices, post_slice_index, pre_vertex_slice, post_vertex_slice, app_edge, n_synapse_types, single_synapses, master_pop_table_region, weight_scales, machine_time_step, routing_info.get_routing_info_for_edge(machine_edge), all_syn_block_sz, block_addr, single_addr, machine_edge=machine_edge) self._poptable_type.finish_master_pop_table(spec, master_pop_table_region) # Write the size and data of single synapses to the end of the region spec.switch_write_focus(synaptic_matrix_region) if single_synapses: single_data = numpy.concatenate(single_synapses) spec.write_value(len(single_data) * 4) spec.write_array(single_data) else: spec.write_value(0) # Write the position of the single synapses spec.set_write_pointer(0) spec.write_value(block_addr) def __write_row(self, spec, synaptic_matrix_region, synapse_info, pre_slices, pre_slice_idx, post_slices, post_slice_index, pre_vertex_slice, post_vertex_slice, app_edge, n_synapse_types, single_synapses, master_pop_table_region, weight_scales, machine_time_step, rinfo, all_syn_block_sz, block_addr, single_addr, machine_edge): (row_data, row_length, delayed_row_data, delayed_row_length, delayed_source_ids, delay_stages) = self._synapse_io.get_synapses( synapse_info, pre_slices, pre_slice_idx, post_slices, post_slice_index, pre_vertex_slice, post_vertex_slice, app_edge.n_delay_stages, self._poptable_type, n_synapse_types, weight_scales, machine_time_step, app_edge=app_edge, machine_edge=machine_edge) if app_edge.delay_edge is not None: app_edge.delay_edge.pre_vertex.add_delays(pre_vertex_slice, delayed_source_ids, delay_stages) elif delayed_source_ids.size != 0: raise Exception("Found delayed source IDs but no delay " "machine edge for {}".format(app_edge.label)) if (app_edge, synapse_info) in self._pre_run_connection_holders: for conn_holder in self._pre_run_connection_holders[app_edge, synapse_info]: conn_holder.add_connections( self._synapse_io.read_synapses( synapse_info, pre_vertex_slice, post_vertex_slice, row_length, delayed_row_length, n_synapse_types, weight_scales, row_data, delayed_row_data, app_edge.n_delay_stages, machine_time_step)) conn_holder.finish() if row_data.size: block_addr, single_addr = self.__write_row_data( spec, synapse_info, row_length, row_data, rinfo, single_synapses, master_pop_table_region, synaptic_matrix_region, block_addr, single_addr) elif rinfo is not None: self._poptable_type.update_master_population_table( spec, 0, 0, rinfo.first_key_and_mask, master_pop_table_region) del row_data if block_addr > all_syn_block_sz: raise Exception( "Too much synaptic memory has been written: {} of {} ".format( block_addr, all_syn_block_sz)) delay_rinfo = None delay_key = (app_edge.pre_vertex, pre_vertex_slice.lo_atom, pre_vertex_slice.hi_atom) if delay_key in self._delay_key_index: delay_rinfo = self._delay_key_index[delay_key] if delayed_row_data.size: block_addr, single_addr = self.__write_row_data( spec, synapse_info, delayed_row_length, delayed_row_data, delay_rinfo, single_synapses, master_pop_table_region, synaptic_matrix_region, block_addr, single_addr) elif delay_rinfo is not None: self._poptable_type.update_master_population_table( spec, 0, 0, delay_rinfo.first_key_and_mask, master_pop_table_region) del delayed_row_data if block_addr > all_syn_block_sz: raise Exception( "Too much synaptic memory has been written: {} of {} ".format( block_addr, all_syn_block_sz)) return block_addr, single_addr def __write_row_data(self, spec, synapse_info, row_length, row_data, rinfo, single_synapses, master_pop_table_region, synaptic_matrix_region, block_addr, single_addr): if (row_length == 1 and isinstance(synapse_info.connector, OneToOneConnector) and single_addr + len(row_data) <= self._one_to_one_connection_dtcm_max_bytes): single_rows = row_data.reshape(-1, 4)[:, 3] single_synapses.append(single_rows) self._poptable_type.update_master_population_table( spec, single_addr, 1, rinfo.first_key_and_mask, master_pop_table_region, is_single=True) single_addr += len(single_rows) * 4 else: block_addr = self._write_padding(spec, synaptic_matrix_region, block_addr) spec.switch_write_focus(synaptic_matrix_region) spec.write_array(row_data) self._poptable_type.update_master_population_table( spec, block_addr, row_length, rinfo.first_key_and_mask, master_pop_table_region) block_addr += len(row_data) * 4 return block_addr, single_addr def write_data_spec(self, spec, application_vertex, post_vertex_slice, machine_vertex, placement, machine_graph, application_graph, routing_info, graph_mapper, input_type, machine_time_step): # Create an index of delay keys into this vertex for m_edge in machine_graph.get_edges_ending_at_vertex(machine_vertex): app_edge = graph_mapper.get_application_edge(m_edge) if isinstance(app_edge.pre_vertex, DelayExtensionVertex): pre_vertex_slice = graph_mapper.get_slice(m_edge.pre_vertex) self._delay_key_index[app_edge.pre_vertex.source_vertex, pre_vertex_slice.lo_atom, pre_vertex_slice.hi_atom] = \ routing_info.get_routing_info_for_edge(m_edge) post_slices = graph_mapper.get_slices(application_vertex) post_slice_idx = graph_mapper.get_machine_vertex_index(machine_vertex) # Reserve the memory in_edges = machine_graph.get_edges_ending_at_vertex(machine_vertex) all_syn_block_sz = self._get_exact_synaptic_blocks_size( post_slices, post_slice_idx, post_vertex_slice, graph_mapper, in_edges, machine_time_step) self._reserve_memory_regions(spec, machine_vertex, post_vertex_slice, machine_graph, all_syn_block_sz, graph_mapper) weight_scales = self._write_synapse_parameters( spec, machine_vertex, machine_graph, graph_mapper, post_slices, post_slice_idx, post_vertex_slice, input_type, machine_time_step) self._write_synaptic_matrix_and_master_population_table( spec, post_slices, post_slice_idx, machine_vertex, post_vertex_slice, all_syn_block_sz, weight_scales, POPULATION_BASED_REGIONS.POPULATION_TABLE.value, POPULATION_BASED_REGIONS.SYNAPTIC_MATRIX.value, routing_info, graph_mapper, machine_graph, machine_time_step) if isinstance(self._synapse_dynamics, AbstractSynapseDynamicsStructural): self._synapse_dynamics.write_parameters( spec, POPULATION_BASED_REGIONS.SYNAPSE_DYNAMICS.value, machine_time_step, weight_scales, application_graph=application_graph, machine_graph=machine_graph, app_vertex=application_vertex, post_slice=post_vertex_slice, machine_vertex=machine_vertex, graph_mapper=graph_mapper, routing_info=routing_info) else: self._synapse_dynamics.write_parameters( spec, POPULATION_BASED_REGIONS.SYNAPSE_DYNAMICS.value, machine_time_step, weight_scales) self._weight_scales[placement] = weight_scales def clear_connection_cache(self): self._retrieved_blocks = dict() def get_connections_from_machine( self, transceiver, placement, machine_edge, graph_mapper, routing_infos, synapse_info, machine_time_step, using_extra_monitor_cores, placements=None, data_receiver=None, sender_extra_monitor_core_placement=None, extra_monitor_cores_for_router_timeout=None, handle_time_out_configuration=True, fixed_routes=None): app_edge = graph_mapper.get_application_edge(machine_edge) if not isinstance(app_edge, ProjectionApplicationEdge): return None # Get details for extraction pre_vertex_slice = graph_mapper.get_slice(machine_edge.pre_vertex) post_vertex_slice = graph_mapper.get_slice(machine_edge.post_vertex) n_synapse_types = self._synapse_type.get_n_synapse_types() # Get the key for the pre_vertex key = routing_infos.get_first_key_for_edge(machine_edge) # Get the key for the delayed pre_vertex delayed_key = None if app_edge.delay_edge is not None: delayed_key = self._delay_key_index[ app_edge.pre_vertex, pre_vertex_slice.lo_atom, pre_vertex_slice.hi_atom].first_key # Get the block for the connections from the pre_vertex master_pop_table, direct_synapses, indirect_synapses = \ self.__compute_addresses(transceiver, placement) data, max_row_length = self._retrieve_synaptic_block( transceiver, placement, master_pop_table, indirect_synapses, direct_synapses, key, pre_vertex_slice.n_atoms, synapse_info.index, using_extra_monitor_cores, placements, data_receiver, sender_extra_monitor_core_placement, extra_monitor_cores_for_router_timeout, fixed_routes) # Get the block for the connections from the delayed pre_vertex delayed_data = None delayed_max_row_len = 0 if delayed_key is not None: delayed_data, delayed_max_row_len = self._retrieve_synaptic_block( transceiver, placement, master_pop_table, indirect_synapses, direct_synapses, delayed_key, pre_vertex_slice.n_atoms * app_edge.n_delay_stages, synapse_info.index, using_extra_monitor_cores, placements, data_receiver, sender_extra_monitor_core_placement, extra_monitor_cores_for_router_timeout, handle_time_out_configuration, fixed_routes) # Convert the blocks into connections return self._synapse_io.read_synapses( synapse_info, pre_vertex_slice, post_vertex_slice, max_row_length, delayed_max_row_len, n_synapse_types, self._weight_scales[placement], data, delayed_data, app_edge.n_delay_stages, machine_time_step) def __compute_addresses(self, transceiver, placement): """ Helper for computing the addresses of the master pop table and\ synaptic-matrix-related bits. """ master_pop_table = locate_memory_region_for_placement( placement, POPULATION_BASED_REGIONS.POPULATION_TABLE.value, transceiver) synaptic_matrix = locate_memory_region_for_placement( placement, POPULATION_BASED_REGIONS.SYNAPTIC_MATRIX.value, transceiver) direct_synapses = ( self._get_static_synaptic_matrix_sdram_requirements() + synaptic_matrix + _ONE_WORD.unpack_from( transceiver.read_memory(placement.x, placement.y, synaptic_matrix, 4))[0]) indirect_synapses = synaptic_matrix + 4 return master_pop_table, direct_synapses, indirect_synapses def _retrieve_synaptic_block(self, transceiver, placement, master_pop_table_address, indirect_synapses_address, direct_synapses_address, key, n_rows, index, using_extra_monitor_cores, placements=None, data_receiver=None, sender_extra_monitor_core_placement=None, extra_monitor_cores_for_router_timeout=None, handle_time_out_configuration=True, fixed_routes=None): """ Read in a synaptic block from a given processor and vertex on\ the machine """ # See if we have already got this block if (placement, key, index) in self._retrieved_blocks: return self._retrieved_blocks[placement, key, index] items = self._poptable_type.extract_synaptic_matrix_data_location( key, master_pop_table_address, transceiver, placement.x, placement.y) if index >= len(items): return None, None max_row_length, synaptic_block_offset, is_single = items[index] if max_row_length == 0: return None, None block = None if max_row_length > 0 and synaptic_block_offset is not None: # if exploiting the extra monitor cores, need to set the machine # for data extraction mode if using_extra_monitor_cores and handle_time_out_configuration: data_receiver.set_cores_for_data_extraction( transceiver, extra_monitor_cores_for_router_timeout, placements) # read in the synaptic block if not is_single: block = self.__read_multiple_synaptic_blocks( transceiver, data_receiver, placement, n_rows, max_row_length, indirect_synapses_address + synaptic_block_offset, using_extra_monitor_cores, sender_extra_monitor_core_placement, fixed_routes) else: block, max_row_length = self.__read_single_synaptic_block( transceiver, data_receiver, placement, n_rows, direct_synapses_address + synaptic_block_offset, using_extra_monitor_cores, sender_extra_monitor_core_placement, fixed_routes) if using_extra_monitor_cores and handle_time_out_configuration: data_receiver.unset_cores_for_data_extraction( transceiver, extra_monitor_cores_for_router_timeout, placements) self._retrieved_blocks[placement, key, index] = (block, max_row_length) return block, max_row_length def __read_multiple_synaptic_blocks(self, transceiver, data_receiver, placement, n_rows, max_row_length, address, using_extra_monitor_cores, sender_extra_monitor_core_placement, fixed_routes): """ Read in an array of synaptic blocks. """ # calculate the synaptic block size in bytes synaptic_block_size = self._synapse_io.get_block_n_bytes( max_row_length, n_rows) # read in the synaptic block if using_extra_monitor_cores: return data_receiver.get_data(transceiver, sender_extra_monitor_core_placement, address, synaptic_block_size, fixed_routes) return transceiver.read_memory(placement.x, placement.y, address, synaptic_block_size) def __read_single_synaptic_block(self, transceiver, data_receiver, placement, n_rows, address, using_extra_monitor_cores, sender_extra_monitor_core_placement, fixed_routes): """ Read in a single synaptic block. """ # The data is one per row synaptic_block_size = n_rows * 4 # read in the synaptic row data if using_extra_monitor_cores: single_block = data_receiver.get_data( transceiver, sender_extra_monitor_core_placement, address, synaptic_block_size, fixed_routes) else: single_block = transceiver.read_memory(placement.x, placement.y, address, synaptic_block_size) # Convert the block into a set of rows numpy_block = numpy.zeros((n_rows, 4), dtype="uint32") numpy_block[:, 3] = numpy.asarray(single_block, dtype="uint8").view("uint32") numpy_block[:, 1] = 1 return bytearray(numpy_block.tobytes()), 1 # inherited from AbstractProvidesIncomingPartitionConstraints def get_incoming_partition_constraints(self): return self._poptable_type.get_edge_constraints() def read_parameters_from_machine(self, transceiver, placement, vertex_slice): # locate SDRAM address to where the synapse parameters are stored synapse_region_sdram_address = locate_memory_region_for_placement( placement, POPULATION_BASED_REGIONS.SYNAPSE_PARAMS.value, transceiver) # get size of synapse params size_of_region = ( self._synapse_type.get_sdram_usage_per_neuron_in_bytes() * vertex_slice.n_atoms) # get data from the machine byte_array = transceiver.read_memory(placement.x, placement.y, synapse_region_sdram_address, size_of_region) synapse_params, _ = translate_parameters( self._synapse_type.get_synapse_type_parameter_types(), byte_array, 0, vertex_slice) self._synapse_type.set_synapse_type_parameters(synapse_params, vertex_slice) def regenerate_data_specification(self, spec, placement, machine_time_step, time_scale_factor, vertex_slice): spec.reserve_memory_region( region=POPULATION_BASED_REGIONS.SYNAPSE_PARAMS.value, size=self._get_synapse_params_size(vertex_slice), label='SynapseParams') spec.switch_write_focus(POPULATION_BASED_REGIONS.SYNAPSE_PARAMS.value) write_parameters_per_neuron( spec, vertex_slice, self._synapse_type.get_synapse_type_parameters())
def test_write_synaptic_matrix_and_master_population_table(self): MockSimulator.setup() default_config_paths = os.path.join( os.path.dirname(abstract_spinnaker_common.__file__), AbstractSpiNNakerCommon.CONFIG_FILE_NAME) config = conf_loader.load_config( AbstractSpiNNakerCommon.CONFIG_FILE_NAME, default_config_paths) config.set("Simulation", "one_to_one_connection_dtcm_max_bytes", 40) machine_time_step = 1000.0 pre_app_vertex = SimpleApplicationVertex(10) pre_vertex = SimpleMachineVertex(resources=None) pre_vertex_slice = Slice(0, 9) post_app_vertex = SimpleApplicationVertex(10) post_vertex = SimpleMachineVertex(resources=None) post_vertex_slice = Slice(0, 9) post_slice_index = 0 one_to_one_connector_1 = OneToOneConnector(None) one_to_one_connector_1.set_projection_information( pre_app_vertex, post_app_vertex, None, machine_time_step) one_to_one_connector_1.set_weights_and_delays(1.5, 1.0) one_to_one_connector_2 = OneToOneConnector(None) one_to_one_connector_2.set_projection_information( pre_app_vertex, post_app_vertex, None, machine_time_step) one_to_one_connector_2.set_weights_and_delays(2.5, 2.0) all_to_all_connector = AllToAllConnector(None) all_to_all_connector.set_projection_information( pre_app_vertex, post_app_vertex, None, machine_time_step) all_to_all_connector.set_weights_and_delays(4.5, 4.0) direct_synapse_information_1 = SynapseInformation( one_to_one_connector_1, SynapseDynamicsStatic(), 0) direct_synapse_information_2 = SynapseInformation( one_to_one_connector_2, SynapseDynamicsStatic(), 1) all_to_all_synapse_information = SynapseInformation( all_to_all_connector, SynapseDynamicsStatic(), 0) app_edge = ProjectionApplicationEdge(pre_app_vertex, post_app_vertex, direct_synapse_information_1) app_edge.add_synapse_information(direct_synapse_information_2) app_edge.add_synapse_information(all_to_all_synapse_information) machine_edge = ProjectionMachineEdge(app_edge.synapse_information, pre_vertex, post_vertex) partition_name = "TestPartition" graph = MachineGraph("Test") graph.add_vertex(pre_vertex) graph.add_vertex(post_vertex) graph.add_edge(machine_edge, partition_name) graph_mapper = GraphMapper() graph_mapper.add_vertex_mapping(pre_vertex, pre_vertex_slice, pre_app_vertex) graph_mapper.add_vertex_mapping(post_vertex, post_vertex_slice, post_app_vertex) graph_mapper.add_edge_mapping(machine_edge, app_edge) weight_scales = [4096.0, 4096.0] key = 0 routing_info = RoutingInfo() routing_info.add_partition_info( PartitionRoutingInfo( [BaseKeyAndMask(key, 0xFFFFFFF0)], graph.get_outgoing_edge_partition_starting_at_vertex( pre_vertex, partition_name))) temp_spec = tempfile.mktemp() spec_writer = FileDataWriter(temp_spec) spec = DataSpecificationGenerator(spec_writer, None) master_pop_sz = 1000 master_pop_region = 0 all_syn_block_sz = 2000 synapse_region = 1 spec.reserve_memory_region(master_pop_region, master_pop_sz) spec.reserve_memory_region(synapse_region, all_syn_block_sz) synapse_type = MockSynapseType() synaptic_manager = SynapticManager(synapse_type=synapse_type, ring_buffer_sigma=5.0, spikes_per_second=100.0, config=config) synaptic_manager._write_synaptic_matrix_and_master_population_table( spec, [post_vertex_slice], post_slice_index, post_vertex, post_vertex_slice, all_syn_block_sz, weight_scales, master_pop_region, synapse_region, routing_info, graph_mapper, graph, machine_time_step) spec.end_specification() spec_writer.close() spec_reader = FileDataReader(temp_spec) executor = DataSpecificationExecutor(spec_reader, master_pop_sz + all_syn_block_sz) executor.execute() master_pop_table = executor.get_region(0) synaptic_matrix = executor.get_region(1) all_data = bytearray() all_data.extend( master_pop_table.region_data[:master_pop_table.max_write_pointer]) all_data.extend( synaptic_matrix.region_data[:synaptic_matrix.max_write_pointer]) master_pop_table_address = 0 synaptic_matrix_address = master_pop_table.max_write_pointer direct_synapses_address = struct.unpack_from( "<I", synaptic_matrix.region_data)[0] direct_synapses_address += synaptic_matrix_address + 8 indirect_synapses_address = synaptic_matrix_address + 4 placement = Placement(None, 0, 0, 1) transceiver = MockTransceiverRawData(all_data) # Get the master population table details items = synaptic_manager._poptable_type\ .extract_synaptic_matrix_data_location( key, master_pop_table_address, transceiver, placement.x, placement.y) # The first entry should be direct, but the rest should be indirect; # the second is potentially direct, but has been restricted by the # restriction on the size of the direct matrix assert len(items) == 3 # TODO: This has been changed because direct matrices are disabled! assert not items[0][2] assert not items[1][2] assert not items[2][2] data_1, row_len_1 = synaptic_manager._retrieve_synaptic_block( transceiver=transceiver, placement=placement, master_pop_table_address=master_pop_table_address, indirect_synapses_address=indirect_synapses_address, direct_synapses_address=direct_synapses_address, key=key, n_rows=pre_vertex_slice.n_atoms, index=0, using_extra_monitor_cores=False) connections_1 = synaptic_manager._synapse_io.read_synapses( direct_synapse_information_1, pre_vertex_slice, post_vertex_slice, row_len_1, 0, 2, weight_scales, data_1, None, app_edge.n_delay_stages, machine_time_step) # The first matrix is a 1-1 matrix, so row length is 1 assert row_len_1 == 1 # Check that all the connections have the right weight and delay assert len(connections_1) == post_vertex_slice.n_atoms assert all([conn["weight"] == 1.5 for conn in connections_1]) assert all([conn["delay"] == 1.0 for conn in connections_1]) data_2, row_len_2 = synaptic_manager._retrieve_synaptic_block( transceiver=transceiver, placement=placement, master_pop_table_address=master_pop_table_address, indirect_synapses_address=indirect_synapses_address, direct_synapses_address=direct_synapses_address, key=key, n_rows=pre_vertex_slice.n_atoms, index=1, using_extra_monitor_cores=False) connections_2 = synaptic_manager._synapse_io.read_synapses( direct_synapse_information_2, pre_vertex_slice, post_vertex_slice, row_len_2, 0, 2, weight_scales, data_2, None, app_edge.n_delay_stages, machine_time_step) # The second matrix is a 1-1 matrix, so row length is 1 assert row_len_2 == 1 # Check that all the connections have the right weight and delay assert len(connections_2) == post_vertex_slice.n_atoms assert all([conn["weight"] == 2.5 for conn in connections_2]) assert all([conn["delay"] == 2.0 for conn in connections_2]) data_3, row_len_3 = synaptic_manager._retrieve_synaptic_block( transceiver=transceiver, placement=placement, master_pop_table_address=master_pop_table_address, indirect_synapses_address=indirect_synapses_address, direct_synapses_address=direct_synapses_address, key=key, n_rows=pre_vertex_slice.n_atoms, index=2, using_extra_monitor_cores=False) connections_3 = synaptic_manager._synapse_io.read_synapses( all_to_all_synapse_information, pre_vertex_slice, post_vertex_slice, row_len_3, 0, 2, weight_scales, data_3, None, app_edge.n_delay_stages, machine_time_step) # The third matrix is an all-to-all matrix, so length is n_atoms assert row_len_3 == post_vertex_slice.n_atoms # Check that all the connections have the right weight and delay assert len(connections_3) == \ post_vertex_slice.n_atoms * pre_vertex_slice.n_atoms assert all([conn["weight"] == 4.5 for conn in connections_3]) assert all([conn["delay"] == 4.0 for conn in connections_3])
def test_set_synapse_dynamics(self): MockSimulator.setup() default_config_paths = os.path.join( os.path.dirname(abstract_spinnaker_common.__file__), AbstractSpiNNakerCommon.CONFIG_FILE_NAME) config = conf_loader.load_config( AbstractSpiNNakerCommon.CONFIG_FILE_NAME, default_config_paths) synaptic_manager = SynapticManager( n_synapse_types=2, ring_buffer_sigma=5.0, spikes_per_second=100.0, config=config) static = SynapseDynamicsStatic() stdp = SynapseDynamicsSTDP( timing_dependence=TimingDependenceSpikePair(), weight_dependence=WeightDependenceAdditive()) alt_stdp = SynapseDynamicsSTDP( timing_dependence=TimingDependenceSpikePair(), weight_dependence=WeightDependenceMultiplicative()) static_struct = SynapseDynamicsStructuralStatic( partner_selection=LastNeuronSelection(), formation=DistanceDependentFormation(), elimination=RandomByWeightElimination(0.5)) alt_static_struct = SynapseDynamicsStructuralStatic( partner_selection=RandomSelection(), formation=DistanceDependentFormation(), elimination=RandomByWeightElimination(0.5)) stdp_struct = SynapseDynamicsStructuralSTDP( partner_selection=LastNeuronSelection(), formation=DistanceDependentFormation(), elimination=RandomByWeightElimination(0.5), timing_dependence=TimingDependenceSpikePair(), weight_dependence=WeightDependenceAdditive()) alt_stdp_struct = SynapseDynamicsStructuralSTDP( partner_selection=RandomSelection(), formation=DistanceDependentFormation(), elimination=RandomByWeightElimination(0.5), timing_dependence=TimingDependenceSpikePair(), weight_dependence=WeightDependenceAdditive()) alt_stdp_struct_2 = SynapseDynamicsStructuralSTDP( partner_selection=LastNeuronSelection(), formation=DistanceDependentFormation(), elimination=RandomByWeightElimination(0.5), timing_dependence=TimingDependenceSpikePair(), weight_dependence=WeightDependenceMultiplicative()) # This should be fine as it is the first call synaptic_manager.synapse_dynamics = static # This should be fine as STDP overrides static synaptic_manager.synapse_dynamics = stdp # This should fail because STDP dependences are difference with self.assertRaises(SynapticConfigurationException): synaptic_manager.synapse_dynamics = alt_stdp # This should work because STDP dependences are the same synaptic_manager.synapse_dynamics = stdp # This should work because static always works, but the type should # still be STDP synaptic_manager.synapse_dynamics = static self.assertIsInstance( synaptic_manager.synapse_dynamics, SynapseDynamicsSTDP) # This should work but should merge with the STDP rule synaptic_manager.synapse_dynamics = static_struct self.assertIsInstance( synaptic_manager.synapse_dynamics, SynapseDynamicsStructuralSTDP) # These should work as static / the STDP is the same but neither should # change anything synaptic_manager.synapse_dynamics = static self.assertIsInstance( synaptic_manager.synapse_dynamics, SynapseDynamicsStructuralSTDP) synaptic_manager.synapse_dynamics = stdp self.assertIsInstance( synaptic_manager.synapse_dynamics, SynapseDynamicsStructuralSTDP) synaptic_manager.synapse_dynamics = static_struct self.assertIsInstance( synaptic_manager.synapse_dynamics, SynapseDynamicsStructuralSTDP) # These should fail as things are different with self.assertRaises(SynapticConfigurationException): synaptic_manager.synapse_dynamics = alt_static_struct with self.assertRaises(SynapticConfigurationException): synaptic_manager.synapse_dynamics = alt_stdp # This should pass as same structural STDP synaptic_manager.synapse_dynamics = stdp_struct self.assertIsInstance( synaptic_manager.synapse_dynamics, SynapseDynamicsStructuralSTDP) # These should fail as both different with self.assertRaises(SynapticConfigurationException): synaptic_manager.synapse_dynamics = alt_stdp_struct with self.assertRaises(SynapticConfigurationException): synaptic_manager.synapse_dynamics = alt_stdp_struct_2 # Try starting again to get a couple more combinations synaptic_manager = SynapticManager( n_synapse_types=2, ring_buffer_sigma=5.0, spikes_per_second=100.0, config=config) # STDP followed by structural STDP should result in Structural STDP synaptic_manager.synapse_dynamics = stdp synaptic_manager.synapse_dynamics = stdp_struct self.assertIsInstance( synaptic_manager.synapse_dynamics, SynapseDynamicsStructuralSTDP) # ... and should fail here because of differences with self.assertRaises(SynapticConfigurationException): synaptic_manager.synapse_dynamics = alt_stdp with self.assertRaises(SynapticConfigurationException): synaptic_manager.synapse_dynamics = alt_static_struct with self.assertRaises(SynapticConfigurationException): synaptic_manager.synapse_dynamics = alt_stdp_struct with self.assertRaises(SynapticConfigurationException): synaptic_manager.synapse_dynamics = alt_stdp_struct_2 # One more time! synaptic_manager = SynapticManager( n_synapse_types=2, ring_buffer_sigma=5.0, spikes_per_second=100.0, config=config) # Static followed by static structural should result in static # structural synaptic_manager.synapse_dynamics = static synaptic_manager.synapse_dynamics = static_struct self.assertIsInstance( synaptic_manager.synapse_dynamics, SynapseDynamicsStructuralStatic) # ... and should fail here because of differences with self.assertRaises(SynapticConfigurationException): synaptic_manager.synapse_dynamics = alt_static_struct with self.assertRaises(SynapticConfigurationException): synaptic_manager.synapse_dynamics = alt_stdp_struct # This should be fine synaptic_manager.synapse_dynamics = static # This should be OK, but should merge with STDP (opposite of above) synaptic_manager.synapse_dynamics = stdp self.assertIsInstance( synaptic_manager.synapse_dynamics, SynapseDynamicsStructuralSTDP) # ... and now these should fail with self.assertRaises(SynapticConfigurationException): synaptic_manager.synapse_dynamics = alt_stdp with self.assertRaises(SynapticConfigurationException): synaptic_manager.synapse_dynamics = alt_static_struct with self.assertRaises(SynapticConfigurationException): synaptic_manager.synapse_dynamics = alt_stdp_struct with self.assertRaises(SynapticConfigurationException): synaptic_manager.synapse_dynamics = alt_stdp_struct_2 # OK, just one more, honest synaptic_manager = SynapticManager( n_synapse_types=2, ring_buffer_sigma=5.0, spikes_per_second=100.0, config=config) synaptic_manager.synapse_dynamics = static_struct synaptic_manager.synapse_dynamics = stdp_struct
def __init__(self, pre_synaptic_population, post_synaptic_population, connector, synapse_type=None, source=None, receptor_type=None, space=None, label=None): """ :param ~spynnaker.pyNN.models.populations.PopulationBase \ pre_synaptic_population: :param ~spynnaker.pyNN.models.populations.PopulationBase \ post_synaptic_population: :param AbstractConnector connector: :param AbstractSynapseDynamics synapse_type: :param None source: Unsupported; must be None :param str receptor_type: :param ~pyNN.space.Space space: :param str label: """ # pylint: disable=too-many-arguments, too-many-locals if source is not None: raise NotImplementedError( "sPyNNaker {} does not yet support multi-compartmental " "cells.".format(__version__)) sim = get_simulator() self.__projection_edge = None self.__host_based_synapse_list = None self.__has_retrieved_synaptic_list_from_machine = False self.__requires_mapping = True self.__label = label pre_is_view = self.__check_population(pre_synaptic_population, connector) post_is_view = self.__check_population(post_synaptic_population, connector) # set default label if label is None: # set the projection's label to a default (maybe non-unique!) self.__label = ("from pre {} to post {} with connector {}".format( pre_synaptic_population.label, post_synaptic_population.label, connector)) # give an auto generated label for the underlying edge label = "projection edge {}".format(sim.none_labelled_edge_count) sim.increment_none_labelled_edge_count() # Handle default synapse type if synapse_type is None: synapse_dynamics = SynapseDynamicsStatic() else: synapse_dynamics = synapse_type # set the space function as required if space is None: space = PyNNSpace() connector.set_space(space) pre_vertex = pre_synaptic_population._vertex post_vertex = post_synaptic_population._vertex if not isinstance(post_vertex, AbstractAcceptsIncomingSynapses): raise ConfigurationException( "postsynaptic population is not designed to receive" " synaptic projections") # sort out synapse type synaptic_type = post_vertex.get_synapse_id_by_target(receptor_type) synapse_type_from_dynamics = False if synaptic_type is None: synaptic_type = synapse_dynamics.get_synapse_id_by_target( receptor_type) synapse_type_from_dynamics = True if synaptic_type is None: raise ConfigurationException( "Synapse target {} not found in {}".format( receptor_type, post_synaptic_population.label)) # as a from-list connector can have plastic parameters, grab those ( # if any) and add them to the synapse dynamics object if isinstance(connector, FromListConnector): connector._apply_parameters_to_synapse_type(synaptic_type) # round the delays to multiples of full timesteps # (otherwise SDRAM estimation calculations can go wrong) if ((not isinstance(synapse_dynamics.delay, RandomDistribution)) and (not isinstance(synapse_dynamics.delay, str))): synapse_dynamics.set_delay( numpy.rint( numpy.array(synapse_dynamics.delay) * machine_time_step_per_ms()) * machine_time_step_ms()) # set the plasticity dynamics for the post pop (allows plastic stuff # when needed) post_vertex.set_synapse_dynamics(synapse_dynamics) # get rng if needed rng = connector.rng if hasattr(connector, "rng") else None # Set and store synapse information for future processing self.__synapse_information = SynapseInformation( connector, pre_synaptic_population, post_synaptic_population, pre_is_view, post_is_view, rng, synapse_dynamics, synaptic_type, receptor_type, sim.use_virtual_board, synapse_type_from_dynamics, synapse_dynamics.weight, synapse_dynamics.delay) # Set projection information in connector connector.set_projection_information(self.__synapse_information) # Find out if there is an existing edge between the populations edge_to_merge = self._find_existing_edge(pre_vertex, post_vertex) if edge_to_merge is not None: # If there is an existing edge, add the connector edge_to_merge.add_synapse_information(self.__synapse_information) self.__projection_edge = edge_to_merge else: # If there isn't an existing edge, create a new one and add it self.__projection_edge = ProjectionApplicationEdge( pre_vertex, post_vertex, self.__synapse_information, label=label) sim.add_application_edge(self.__projection_edge, SPIKE_PARTITION_ID) # add projection to the SpiNNaker control system sim.add_projection(self) # If there is a virtual board, we need to hold the data in case the # user asks for it self.__virtual_connection_list = None if sim.use_virtual_board: self.__virtual_connection_list = list() connection_holder = ConnectionHolder( None, False, pre_vertex.n_atoms, post_vertex.n_atoms, self.__virtual_connection_list) self.__synapse_information.add_pre_run_connection_holder( connection_holder) # If the target is a population, add to the list of incoming # projections if isinstance(post_vertex, AbstractPopulationVertex): post_vertex.add_incoming_projection(self) # If the source is a poisson, add to the list of outgoing projections if isinstance(pre_vertex, SpikeSourcePoissonVertex): pre_vertex.add_outgoing_projection(self)
def test_set_synapse_dynamics(): unittest_setup() p.setup(1.0) post_app_model = IFCurrExpBase() post_app_vertex = post_app_model.create_vertex( n_neurons=10, label="post", constraints=None, spikes_per_second=None, ring_buffer_sigma=None, incoming_spike_buffer_size=None, n_steps_per_timestep=1, drop_late_spikes=True, splitter=None) static = SynapseDynamicsStatic() stdp = SynapseDynamicsSTDP(timing_dependence=TimingDependenceSpikePair(), weight_dependence=WeightDependenceAdditive()) alt_stdp = SynapseDynamicsSTDP( timing_dependence=TimingDependenceSpikePair(), weight_dependence=WeightDependenceMultiplicative()) static_struct = SynapseDynamicsStructuralStatic( partner_selection=LastNeuronSelection(), formation=DistanceDependentFormation(), elimination=RandomByWeightElimination(0.5)) alt_static_struct = SynapseDynamicsStructuralStatic( partner_selection=RandomSelection(), formation=DistanceDependentFormation(), elimination=RandomByWeightElimination(0.5)) stdp_struct = SynapseDynamicsStructuralSTDP( partner_selection=LastNeuronSelection(), formation=DistanceDependentFormation(), elimination=RandomByWeightElimination(0.5), timing_dependence=TimingDependenceSpikePair(), weight_dependence=WeightDependenceAdditive()) alt_stdp_struct = SynapseDynamicsStructuralSTDP( partner_selection=RandomSelection(), formation=DistanceDependentFormation(), elimination=RandomByWeightElimination(0.5), timing_dependence=TimingDependenceSpikePair(), weight_dependence=WeightDependenceAdditive()) alt_stdp_struct_2 = SynapseDynamicsStructuralSTDP( partner_selection=LastNeuronSelection(), formation=DistanceDependentFormation(), elimination=RandomByWeightElimination(0.5), timing_dependence=TimingDependenceSpikePair(), weight_dependence=WeightDependenceMultiplicative()) neuromodulation = SynapseDynamicsNeuromodulation() alt_neuromodulation = SynapseDynamicsNeuromodulation(tau_c=1) # This should be fine as it is the first call post_app_vertex.synapse_dynamics = static # This should fail as can't add neuromodulation first with pytest.raises(SynapticConfigurationException): post_app_vertex.synapse_dynamics = neuromodulation # This should be fine as STDP overrides static post_app_vertex.synapse_dynamics = stdp # This should fail because STDP dependences are difference with pytest.raises(SynapticConfigurationException): post_app_vertex.synapse_dynamics = alt_stdp # This should pass because neuromodulation is OK after STDP post_app_vertex.synapse_dynamics = neuromodulation assert isinstance(post_app_vertex.synapse_dynamics, SynapseDynamicsSTDP) # This should work because STDP dependences are the same post_app_vertex.synapse_dynamics = stdp # This should fail as neuromodulation type is different with pytest.raises(SynapticConfigurationException): post_app_vertex.synapse_dynamics = alt_neuromodulation # This should be fine as same neuromodulation post_app_vertex.synapse_dynamics = neuromodulation assert isinstance(post_app_vertex.synapse_dynamics, SynapseDynamicsSTDP) # This should work because static always works, but the type should # still be STDP post_app_vertex.synapse_dynamics = static assert isinstance(post_app_vertex.synapse_dynamics, SynapseDynamicsSTDP) # This should work but should merge with the STDP rule post_app_vertex.synapse_dynamics = static_struct assert isinstance(post_app_vertex.synapse_dynamics, SynapseDynamicsStructuralSTDP) # These should work as static / the STDP is the same but neither should # change anything post_app_vertex.synapse_dynamics = static assert isinstance(post_app_vertex.synapse_dynamics, SynapseDynamicsStructuralSTDP) post_app_vertex.synapse_dynamics = stdp assert isinstance(post_app_vertex.synapse_dynamics, SynapseDynamicsStructuralSTDP) post_app_vertex.synapse_dynamics = static_struct assert isinstance(post_app_vertex.synapse_dynamics, SynapseDynamicsStructuralSTDP) # These should fail as things are different with pytest.raises(SynapticConfigurationException): post_app_vertex.synapse_dynamics = alt_static_struct with pytest.raises(SynapticConfigurationException): post_app_vertex.synapse_dynamics = alt_stdp # This should pass as same structural STDP post_app_vertex.synapse_dynamics = stdp_struct assert isinstance(post_app_vertex.synapse_dynamics, SynapseDynamicsStructuralSTDP) # These should fail as both different with pytest.raises(SynapticConfigurationException): post_app_vertex.synapse_dynamics = alt_stdp_struct with pytest.raises(SynapticConfigurationException): post_app_vertex.synapse_dynamics = alt_stdp_struct_2 # Try starting again to get a couple more combinations post_app_vertex = post_app_model.create_vertex( n_neurons=10, label="post", constraints=None, spikes_per_second=None, ring_buffer_sigma=None, incoming_spike_buffer_size=None, n_steps_per_timestep=1, drop_late_spikes=True, splitter=None) # STDP followed by structural STDP should result in Structural STDP post_app_vertex.synapse_dynamics = stdp post_app_vertex.synapse_dynamics = stdp_struct assert isinstance(post_app_vertex.synapse_dynamics, SynapseDynamicsStructuralSTDP) # ... and should fail here because of differences with pytest.raises(SynapticConfigurationException): post_app_vertex.synapse_dynamics = alt_stdp with pytest.raises(SynapticConfigurationException): post_app_vertex.synapse_dynamics = alt_static_struct with pytest.raises(SynapticConfigurationException): post_app_vertex.synapse_dynamics = alt_stdp_struct with pytest.raises(SynapticConfigurationException): post_app_vertex.synapse_dynamics = alt_stdp_struct_2 # One more time! post_app_vertex = post_app_model.create_vertex( n_neurons=10, label="post", constraints=None, spikes_per_second=None, ring_buffer_sigma=None, incoming_spike_buffer_size=None, n_steps_per_timestep=1, drop_late_spikes=True, splitter=None) # Static followed by static structural should result in static # structural post_app_vertex.synapse_dynamics = static post_app_vertex.synapse_dynamics = static_struct assert isinstance(post_app_vertex.synapse_dynamics, SynapseDynamicsStructuralStatic) # ... and should fail here because of differences with pytest.raises(SynapticConfigurationException): post_app_vertex.synapse_dynamics = alt_static_struct with pytest.raises(SynapticConfigurationException): post_app_vertex.synapse_dynamics = alt_stdp_struct # This should be fine post_app_vertex.synapse_dynamics = static # This should be OK, but should merge with STDP (opposite of above) post_app_vertex.synapse_dynamics = stdp assert isinstance(post_app_vertex.synapse_dynamics, SynapseDynamicsStructuralSTDP) # ... and now these should fail with pytest.raises(SynapticConfigurationException): post_app_vertex.synapse_dynamics = alt_stdp with pytest.raises(SynapticConfigurationException): post_app_vertex.synapse_dynamics = alt_static_struct with pytest.raises(SynapticConfigurationException): post_app_vertex.synapse_dynamics = alt_stdp_struct with pytest.raises(SynapticConfigurationException): post_app_vertex.synapse_dynamics = alt_stdp_struct_2 # OK, just one more, honest post_app_vertex = post_app_model.create_vertex( n_neurons=10, label="post", constraints=None, spikes_per_second=None, ring_buffer_sigma=None, incoming_spike_buffer_size=None, n_steps_per_timestep=1, drop_late_spikes=True, splitter=None) post_app_vertex.synapse_dynamics = static_struct post_app_vertex.synapse_dynamics = stdp_struct
def test_write_synaptic_matrix_and_master_population_table(self): MockSimulator.setup() # Add an sdram so max SDRAM is high enough SDRAM(10000) # UGLY but the mock transceiver NEED generate_on_machine to be False AbstractGenerateConnectorOnMachine.generate_on_machine = self.say_false default_config_paths = os.path.join( os.path.dirname(abstract_spinnaker_common.__file__), AbstractSpiNNakerCommon.CONFIG_FILE_NAME) config = conf_loader.load_config( AbstractSpiNNakerCommon.CONFIG_FILE_NAME, default_config_paths) config.set("Simulation", "one_to_one_connection_dtcm_max_bytes", 40) machine_time_step = 1000.0 pre_app_vertex = SimpleApplicationVertex(10) pre_vertex_slice = Slice(0, 9) pre_vertex = pre_app_vertex.create_machine_vertex( pre_vertex_slice, None) post_app_vertex = SimpleApplicationVertex(10) post_vertex_slice = Slice(0, 9) post_vertex = post_app_vertex.create_machine_vertex( post_vertex_slice, None) post_slice_index = 0 one_to_one_connector_1 = OneToOneConnector(None) direct_synapse_information_1 = SynapseInformation( one_to_one_connector_1, pre_app_vertex, post_app_vertex, False, False, None, SynapseDynamicsStatic(), 0, 1.5, 1.0) one_to_one_connector_1.set_projection_information( machine_time_step, direct_synapse_information_1) one_to_one_connector_2 = OneToOneConnector(None) direct_synapse_information_2 = SynapseInformation( one_to_one_connector_2, pre_app_vertex, post_app_vertex, False, False, None, SynapseDynamicsStatic(), 1, 2.5, 2.0) one_to_one_connector_2.set_projection_information( machine_time_step, direct_synapse_information_2) all_to_all_connector = AllToAllConnector(None) all_to_all_synapse_information = SynapseInformation( all_to_all_connector, pre_app_vertex, post_app_vertex, False, False, None, SynapseDynamicsStatic(), 0, 4.5, 4.0) all_to_all_connector.set_projection_information( machine_time_step, all_to_all_synapse_information) app_edge = ProjectionApplicationEdge(pre_app_vertex, post_app_vertex, direct_synapse_information_1) app_edge.add_synapse_information(direct_synapse_information_2) app_edge.add_synapse_information(all_to_all_synapse_information) machine_edge = app_edge.create_machine_edge(pre_vertex, post_vertex, label=None) partition_name = "TestPartition" graph = MachineGraph("Test") graph.add_vertex(pre_vertex) graph.add_vertex(post_vertex) graph.add_edge(machine_edge, partition_name) weight_scales = [4096.0, 4096.0] key = 0 routing_info = RoutingInfo() routing_info.add_partition_info( PartitionRoutingInfo( [BaseKeyAndMask(key, 0xFFFFFFF0)], graph.get_outgoing_edge_partition_starting_at_vertex( pre_vertex, partition_name))) temp_spec = tempfile.mktemp() spec_writer = FileDataWriter(temp_spec) spec = DataSpecificationGenerator(spec_writer, None) master_pop_sz = 1000 all_syn_block_sz = 2000 master_pop_region = 0 synapse_region = 1 direct_region = 2 spec.reserve_memory_region(master_pop_region, master_pop_sz) spec.reserve_memory_region(synapse_region, all_syn_block_sz) synaptic_manager = SynapticManager(n_synapse_types=2, ring_buffer_sigma=5.0, spikes_per_second=100.0, config=config) # Poke in our testing region IDs synaptic_manager._pop_table_region = master_pop_region synaptic_manager._synaptic_matrix_region = synapse_region synaptic_manager._direct_matrix_region = direct_region synaptic_manager._write_synaptic_matrix_and_master_population_table( spec, [post_vertex_slice], post_slice_index, post_vertex, post_vertex_slice, all_syn_block_sz, weight_scales, routing_info, graph, machine_time_step) spec.end_specification() spec_writer.close() spec_reader = FileDataReader(temp_spec) executor = DataSpecificationExecutor(spec_reader, master_pop_sz + all_syn_block_sz) executor.execute() master_pop_table = executor.get_region(0) synaptic_matrix = executor.get_region(1) direct_matrix = executor.get_region(2) all_data = bytearray() all_data.extend( master_pop_table.region_data[:master_pop_table.max_write_pointer]) all_data.extend( synaptic_matrix.region_data[:synaptic_matrix.max_write_pointer]) all_data.extend( direct_matrix.region_data[:direct_matrix.max_write_pointer]) master_pop_table_address = 0 synaptic_matrix_address = master_pop_table.max_write_pointer direct_synapses_address = (synaptic_matrix_address + synaptic_matrix.max_write_pointer) direct_synapses_address += 4 indirect_synapses_address = synaptic_matrix_address placement = Placement(None, 0, 0, 1) transceiver = MockTransceiverRawData(all_data) # Get the master population table details items = synaptic_manager._extract_synaptic_matrix_data_location( key, master_pop_table_address, transceiver, placement) # The first entry should be direct, but the rest should be indirect; # the second is potentially direct, but has been restricted by the # restriction on the size of the direct matrix assert len(items) == 3 assert items[0][2] assert not items[1][2] assert not items[2][2] data_1, row_len_1 = synaptic_manager._retrieve_synaptic_block( txrx=transceiver, placement=placement, master_pop_table_address=master_pop_table_address, indirect_synapses_address=indirect_synapses_address, direct_synapses_address=direct_synapses_address, key=key, n_rows=pre_vertex_slice.n_atoms, index=0, using_monitors=False) connections_1 = synaptic_manager._read_synapses( direct_synapse_information_1, pre_vertex_slice, post_vertex_slice, row_len_1, 0, weight_scales, data_1, None, machine_time_step) # The first matrix is a 1-1 matrix, so row length is 1 assert row_len_1 == 1 # Check that all the connections have the right weight and delay assert len(connections_1) == post_vertex_slice.n_atoms assert all([conn["weight"] == 1.5 for conn in connections_1]) assert all([conn["delay"] == 1.0 for conn in connections_1]) data_2, row_len_2 = synaptic_manager._retrieve_synaptic_block( txrx=transceiver, placement=placement, master_pop_table_address=master_pop_table_address, indirect_synapses_address=indirect_synapses_address, direct_synapses_address=direct_synapses_address, key=key, n_rows=pre_vertex_slice.n_atoms, index=1, using_monitors=False) connections_2 = synaptic_manager._read_synapses( direct_synapse_information_2, pre_vertex_slice, post_vertex_slice, row_len_2, 0, weight_scales, data_2, None, machine_time_step) # The second matrix is a 1-1 matrix, so row length is 1 assert row_len_2 == 1 # Check that all the connections have the right weight and delay assert len(connections_2) == post_vertex_slice.n_atoms assert all([conn["weight"] == 2.5 for conn in connections_2]) assert all([conn["delay"] == 2.0 for conn in connections_2]) data_3, row_len_3 = synaptic_manager._retrieve_synaptic_block( txrx=transceiver, placement=placement, master_pop_table_address=master_pop_table_address, indirect_synapses_address=indirect_synapses_address, direct_synapses_address=direct_synapses_address, key=key, n_rows=pre_vertex_slice.n_atoms, index=2, using_monitors=False) connections_3 = synaptic_manager._read_synapses( all_to_all_synapse_information, pre_vertex_slice, post_vertex_slice, row_len_3, 0, weight_scales, data_3, None, machine_time_step) # The third matrix is an all-to-all matrix, so length is n_atoms assert row_len_3 == post_vertex_slice.n_atoms # Check that all the connections have the right weight and delay assert len(connections_3) == \ post_vertex_slice.n_atoms * pre_vertex_slice.n_atoms assert all([conn["weight"] == 4.5 for conn in connections_3]) assert all([conn["delay"] == 4.0 for conn in connections_3])