def __add_entry(self, key_and_mask, core_mask, core_shift, n_neurons): if self.__n_addresses >= _MAX_ADDRESS_START: raise SynapticConfigurationException( "The table already contains {} entries;" " adding another is too many".format(self.__n_addresses)) if key_and_mask.key not in self.__entries: entry = _MasterPopEntry(key_and_mask.key, key_and_mask.mask, core_mask, core_shift, n_neurons) self.__entries[key_and_mask.key] = entry # Need to add an extra "address" for the extra_info if needed if core_mask != 0: self.__n_addresses += 1 return entry entry = self.__entries[key_and_mask.key] if (key_and_mask.mask != entry.mask or core_mask != entry.core_mask or core_shift != entry.core_shift or n_neurons != entry.n_neurons): raise SynapticConfigurationException( "Existing entry for key {} doesn't match one being added:" " Existing mask: {} core_mask: {} core_shift: {}" " n_neurons: {}" " Adding mask: {} core_mask: {} core_shift: {}" " n_neurons: {}".format(key_and_mask.key, entry.mask, entry.core_mask, entry.core_shift, entry.n_neurons, key_and_mask.mask, core_mask, core_shift, n_neurons)) return entry
def merge(self, synapse_dynamics): # If the dynamics is structural, check if same as this if isinstance(synapse_dynamics, AbstractSynapseDynamicsStructural): if not self.is_same_as(synapse_dynamics): raise SynapticConfigurationException( "Synapse dynamics must match exactly when using multiple" " edges to the same population") # If structural part matches, return other as it might also be STDP return synapse_dynamics # If the dynamics is STDP but not Structural (as here), merge if isinstance(synapse_dynamics, SynapseDynamicsSTDP): return SynapseDynamicsStructuralSTDP( self.partner_selection, self.formation, self.elimination, synapse_dynamics.timing_dependence, synapse_dynamics.weight_dependence, # voltage dependence is not supported None, synapse_dynamics.dendritic_delay_fraction, self.f_rew, self.initial_weight, self.initial_delay, self.s_max, self.seed, backprop_delay=synapse_dynamics.backprop_delay) # Otherwise, it is static, so return ourselves return self
def merge(self, synapse_dynamics): # This must replace something that supports neuromodulation, # so it can't be the first thing to be merged! raise SynapticConfigurationException( "Neuromodulation synapses can only be added where an existing" " projection has already been added which supports" " neuromodulation")
def __call__(self, report_folder, connection_holder, dsg_targets): """ Convert synaptic matrix for every application edge. """ # Update the print options to display everything print_opts = numpy.get_printoptions() numpy.set_printoptions(threshold=numpy.nan) if dsg_targets is None: raise SynapticConfigurationException( "dsg_targets should not be none, used as a check for " "connection holder data to be generated") # generate folder for synaptic reports top_level_folder = os.path.join(report_folder, _DIRNAME) if not os.path.exists(top_level_folder): os.mkdir(top_level_folder) # create progress bar progress = ProgressBar(connection_holder.keys(), "Generating synaptic matrix reports") # for each application edge, write matrix in new file for edge, _ in progress.over(connection_holder.keys()): # only write matrix's for edges which have matrix's if isinstance(edge, ProjectionApplicationEdge): # figure new file name file_name = os.path.join(top_level_folder, _TMPL_FILENAME.format(edge.label)) self._write_file(file_name, connection_holder, edge) # Reset the print options numpy.set_printoptions(**print_opts)
def merge(self, synapse_dynamics): # If dynamics is STDP, test if same as if isinstance(synapse_dynamics, SynapseDynamicsSTDP): if not self.is_same_as(synapse_dynamics): raise SynapticConfigurationException( "Synapse dynamics must match exactly when using multiple" " edges to the same population") # If STDP part matches, return the other, as it might also be # structural return synapse_dynamics # If dynamics is structural but not STDP (as here), merge # NOTE: Import here as otherwise we get a circular dependency from .synapse_dynamics_structural_stdp import ( SynapseDynamicsStructuralSTDP) if isinstance(synapse_dynamics, AbstractSynapseDynamicsStructural): return SynapseDynamicsStructuralSTDP( synapse_dynamics.partner_selection, synapse_dynamics.formation, synapse_dynamics.elimination, self.timing_dependence, self.weight_dependence, # voltage dependence is not supported None, self.dendritic_delay_fraction, synapse_dynamics.f_rew, synapse_dynamics.initial_weight, synapse_dynamics.initial_delay, synapse_dynamics.s_max, synapse_dynamics.seed, backprop_delay=self.backprop_delay) # Otherwise, it is static, so return ourselves return self
def merge_neuromodulation(self, neuromodulation): if self.__neuromodulation is None: self.__neuromodulation = neuromodulation elif not self.__neuromodulation.is_neuromodulation_same_as( neuromodulation): raise SynapticConfigurationException( "Neuromodulation must match exactly when using multiple" " edges to the same Population")
def merge(self, synapse_dynamics): # If other is structural, check structural matches if isinstance(synapse_dynamics, AbstractSynapseDynamicsStructural): if not SynapseDynamicsStructuralCommon.is_same_as( self, synapse_dynamics): raise SynapticConfigurationException( "Synapse dynamics must match exactly when using multiple" " edges to the same population") # If other is STDP, check STDP matches if isinstance(synapse_dynamics, SynapseDynamicsSTDP): if not SynapseDynamicsSTDP.is_same_as(self, synapse_dynamics): raise SynapticConfigurationException( "Synapse dynamics must match exactly when using multiple" " edges to the same population") # If everything matches, return ourselves as supreme! return self
def __call__(self, report_folder, connection_holder, dsg_targets): """ converts synaptic matrix for every application edge. """ # Update the print options to display everything import numpy print_opts = numpy.get_printoptions() numpy.set_printoptions(threshold=numpy.nan) if dsg_targets is None: raise SynapticConfigurationException( "dsg targets should not be none, used as a check for " "connection holder data to be generated") # generate folder for synaptic reports top_level_folder = os.path.join(report_folder, "synaptic_matrix_reports") if not os.path.exists(top_level_folder): os.mkdir(top_level_folder) # create progress bar progress = ProgressBar(len(connection_holder.keys()), "Generating synaptic matrix reports") # for each application edge, write matrix in new file for application_edge, _ in connection_holder.keys(): # only write matrix's for edges which have matrix's if isinstance(application_edge, ProjectionApplicationEdge): # figure new file name file_name = os.path.join( top_level_folder, "synaptic_matrix_for_application_edge_{}".format( application_edge.label)) # open writer output = None try: output = open(file_name, "w") except IOError: logger.error("Generate_placement_reports: Can't open file" " {} for writing.".format(file_name)) # write all data for all synapse_information's in same file for info in application_edge.synapse_information: this_connection_holder = connection_holder[( application_edge, info)] output.write("{}".format(this_connection_holder)) output.flush() output.close() progress.update() progress.end() # Reset the print options numpy.set_printoptions(**print_opts)
def merge(self, synapse_dynamics): # Neuromodulation shouldn't be used without STDP if isinstance(synapse_dynamics, SynapseDynamicsNeuromodulation): raise SynapticConfigurationException( "Neuromodulation can only be added when an STDP projection" " has already been added") # We can always override a static synapse dynamics with a more # complex model return synapse_dynamics
def set_sdram_partition(self, sdram_partition): """ Set the SDRAM partition. Must only be called once per instance :param ~pacman.model.graphs.machine\ .SourceSegmentedSDRAMMachinePartition sdram_partition: The SDRAM partition to receive synapses from """ if self.__sdram_partition is not None: raise SynapticConfigurationException( "Trying to set SDRAM partition more than once") self.__sdram_partition = sdram_partition
def add_synapse_information(self, synapse_information): """ :param SynapseInformation synapse_information: """ dynamics = synapse_information.synapse_dynamics is_neuromodulation = are_dynamics_neuromodulation(dynamics) if is_neuromodulation != self.__is_neuromodulation: raise SynapticConfigurationException( "Cannot mix neuromodulated and non-neuromodulated synapses" f" between the same source Population {self._pre_vertex} and" f" target Population {self._post_vertex}") self.__synapse_information.append(synapse_information)
def get_next_allowed_address(self, next_address): """ :param next_address: The next address that would be used :return: The next address that can be used following next_address """ next_address = ((next_address + (self.ADDRESS_SCALE - 1)) // self.ADDRESS_SCALE) * self.ADDRESS_SCALE if (next_address / self.ADDRESS_SCALE) > 0x7FFFFF: raise SynapticConfigurationException( "Address {} is out of range for this population table!".format( hex(next_address))) return next_address
def __update_master_population_table(self, block_start_addr, row_length, key_and_mask, core_mask, core_shift, n_neurons, is_single): """ Add an entry in the binary search to deal with the synaptic matrix :param int block_start_addr: where the synaptic matrix block starts :param int row_length: how long in words each row is :param ~pacman.model.routing_info.BaseKeyAndMask key_and_mask: the key and mask for this master pop entry :param int core_mask: Mask for the part of the key that identifies the core :param int core_shift: The shift of the mask to get to the core_mask :param int n_neurons: The number of neurons in each machine vertex (bar the last) :param bool is_single: Flag that states if the entry is a direct entry for a single row. :return: The index of the entry, to be used to retrieve it :rtype: int :raises ~spynnaker.pyNN.exceptions.SynapticConfigurationException: If a bad address is used. """ # if not single, scale the address start_addr = block_start_addr if not is_single: if block_start_addr % _ADDRESS_SCALE != 0: raise SynapticConfigurationException( "Address {} is not compatible with this table".format( block_start_addr)) start_addr = block_start_addr // _ADDRESS_SCALE if start_addr > _MAX_ADDRESS: raise SynapticConfigurationException( "Address {} is too big for this table".format( block_start_addr)) row_length = self.get_allowed_row_length(row_length) entry = self.__add_entry(key_and_mask, core_mask, core_shift, n_neurons) index = entry.append(start_addr, row_length - 1, is_single) self.__n_addresses += 1 return index
def add_application_entry(self, block_start_addr, row_length, key_and_mask, core_mask, core_shift, n_neurons, address_type=POP_TABLE_ADDRESS_TYPES.SDRAM): """ Add an entry for an application-edge to the population table. :param int block_start_addr: where the synaptic matrix block starts :param int row_length: how long in words each row is :param ~pacman.model.routing_info.BaseKeyAndMask key_and_mask: the key and mask for this master pop entry :param int core_mask: Mask for the part of the key that identifies the core :param int core_shift: The shift of the mask to get to the core_mask :param int n_neurons: The number of neurons in each machine vertex (bar the last) :return: The index of the entry, to be used to retrieve it :rtype: int :raises ~spynnaker.pyNN.exceptions.SynapticConfigurationException: If a bad address is used. """ # If there are too many neurons per core, fail if n_neurons > _MAX_N_NEURONS: raise SynapticConfigurationException( "The parameter n_neurons of {} is too big (maximum {})".format( n_neurons, _MAX_N_NEURONS)) # If the core mask is too big, fail if core_mask > _MAX_CORE_MASK: raise SynapticConfigurationException( "The core mask of {} is too big (maximum {})".format( core_mask, _MAX_CORE_MASK)) return self.__update_master_population_table(block_start_addr, row_length, key_and_mask, core_mask, core_shift, n_neurons, address_type)
def merge(self, synapse_dynamics): # If dynamics is Neuromodulation, merge with other neuromodulation, # and then return ourselves, as neuromodulation can't be used by # itself if isinstance(synapse_dynamics, SynapseDynamicsNeuromodulation): super().merge_neuromodulation(synapse_dynamics) return self # If other is structural, check structural matches if isinstance(synapse_dynamics, AbstractSynapseDynamicsStructural): if not SynapseDynamicsStructuralCommon.is_same_as( self, synapse_dynamics): raise SynapticConfigurationException( "Synapse dynamics must match exactly when using multiple" " edges to the same population") # If other is STDP, check STDP matches if isinstance(synapse_dynamics, SynapseDynamicsSTDP): if not SynapseDynamicsSTDP.is_same_as(self, synapse_dynamics): raise SynapticConfigurationException( "Synapse dynamics must match exactly when using multiple" " edges to the same population") # If everything matches, return ourselves as supreme! return self
def get_next_allowed_address(next_address): """ Get the next allowed address. :param int next_address: The next address that would be used :return: The next address that can be used following next_address :rtype: int :raises ~spynnaker.pyNN.exceptions.SynapticConfigurationException: if the address is out of range """ addr_scaled = (next_address + (_ADDRESS_SCALE - 1)) // _ADDRESS_SCALE if addr_scaled > _MAX_ADDRESS: raise SynapticConfigurationException( "Address {} is out of range for this population table!".format( hex(addr_scaled * _ADDRESS_SCALE))) return addr_scaled * _ADDRESS_SCALE
def add_invalid_application_entry(self, key_and_mask, core_mask=0, core_shift=0, n_neurons=0): """ Add an entry to the table from an application vertex that doesn't point to anywhere. Used to keep indices in synchronisation between e.g. normal and delay entries and between entries on different cores. :param ~pacman.model.routing_info.BaseKeyAndMask key_and_mask: a key_and_mask object used as part of describing an edge that will require being received to be stored in the master pop table; the whole edge will become multiple calls to this function :param int core_mask: Mask for the part of the key that identifies the core :param int core_shift: The shift of the mask to get to the core_mask :param int n_neurons: The number of neurons in each machine vertex (bar the last) :return: The index of the added entry :rtype: int """ # If there are too many neurons per core, fail if n_neurons > _MAX_N_NEURONS: raise SynapticConfigurationException( "The parameter n_neurons of {} is too big (maximum {})".format( n_neurons, _MAX_N_NEURONS)) # If the core mask is too big, fail if core_mask > _MAX_CORE_MASK: raise SynapticConfigurationException( "The core mask of {} is too big (maximum {})".format( core_mask, _MAX_CORE_MASK)) return self.__add_invalid_entry(key_and_mask, core_mask, core_shift, n_neurons)
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")
def get_next_allowed_address(self, next_address): """ :param int next_address: The next address that would be used :return: The next address that can be used following next_address :rtype: int :raises SynapticConfigurationException: if the address is out of range """ # How far is the address past an acceptable boundary? over = next_address % _ADDRESS_SCALE if over: next_address += _ADDRESS_SCALE - over if next_address // _ADDRESS_SCALE > self.TOP_MEMORY_POINT: raise SynapticConfigurationException( "Address {} is out of range for this population table!".format( hex(next_address))) return next_address
def append(self, address, row_length, is_single): """ Add a synaptic matrix pointer to the entry :param int address: The address of the synaptic matrix :param int row_length: The length of each row in the matrix :param bool is_single: True if the address is to the direct matrix :return: The index of the pointer within the entry :rtype: int """ index = len(self.__addresses_and_row_lengths) if index > _MAX_ADDRESS_COUNT: raise SynapticConfigurationException( "{} connections for the same source key (maximum {})".format( index, _MAX_ADDRESS_COUNT)) self.__addresses_and_row_lengths.append( (address, row_length, is_single, True)) return index
def __get_structural_edges(self, application_graph, app_vertex): structural_application_edges = list() for app_edge in application_graph.get_edges_ending_at_vertex( app_vertex): if isinstance(app_edge, ProjectionApplicationEdge): found = False for synapse_info in app_edge.synapse_information: if isinstance(synapse_info.synapse_dynamics, AbstractSynapseDynamicsStructural): if found: raise SynapticConfigurationException( "Only one Projection between each pair of" " Populations can use structural plasticity ") found = True structural_application_edges.append( (app_edge, synapse_info)) return structural_application_edges
def __get_structural_projections(self, incoming_projections): """ :param list(Projection) incoming_projections: Projections to filter to structural only :rtype: list(Projection) """ structural_projections = list() seen_app_edges = set() for proj in incoming_projections: app_edge = proj._projection_edge for synapse_info in app_edge.synapse_information: if isinstance(synapse_info.synapse_dynamics, AbstractSynapseDynamicsStructural): if app_edge in seen_app_edges: raise SynapticConfigurationException(self.PAIR_ERROR) else: seen_app_edges.add(app_edge) structural_projections.append(proj) return structural_projections
def __get_structural_edges(self, app_graph, app_vertex): """ :param ~pacman.model.graphs.application.ApplicationGraph app_graph: :param ~pacman.model.graphs.application.ApplicationVertex app_vertex: :rtype: list(tuple(ProjectionApplicationEdge, SynapseInformation)) """ structural_edges = list() for app_edge in app_graph.get_edges_ending_at_vertex(app_vertex): if isinstance(app_edge, ProjectionApplicationEdge): found = False for synapse_info in app_edge.synapse_information: if isinstance(synapse_info.synapse_dynamics, AbstractSynapseDynamicsStructural): if found: raise SynapticConfigurationException( "Only one Projection between each pair of " "Populations can use structural plasticity") found = True structural_edges.append((app_edge, synapse_info)) return structural_edges
def __call__(self, connection_holder, dsg_targets): """ Convert synaptic matrix for every application edge. :param connection_holder: where the synaptic matrices are stored (possibly after retrieval from the machine) :type connection_holder: dict(tuple(ProjectionApplicationEdge, SynapseInformation), ConnectionHolder) :param dsg_targets: used to check if connection holders are populated """ # Update the print options to display everything print_opts = numpy.get_printoptions() numpy.set_printoptions(threshold=numpy.nan) if dsg_targets is None: raise SynapticConfigurationException( "dsg_targets should not be none, used as a check for " "connection holder data to be generated") # generate folder for synaptic reports top_level_folder = os.path.join(report_default_directory(), _DIRNAME) if not os.path.exists(top_level_folder): os.mkdir(top_level_folder) # create progress bar progress = ProgressBar(connection_holder.keys(), "Generating synaptic matrix reports") # for each application edge, write matrix in new file for edge, _ in progress.over(connection_holder.keys()): # only write matrix's for edges which have matrix's if isinstance(edge, ProjectionApplicationEdge): # figure new file name file_name = os.path.join(top_level_folder, _TMPL_FILENAME.format(edge.label)) self._write_file(file_name, connection_holder, edge) # Reset the print options numpy.set_printoptions(**print_opts)
def __init__(self, weight=StaticSynapse.default_parameters['weight'], tau_c=1000.0, tau_d=200.0, w_min=0.0, w_max=1.0): self.__weight = weight self.__tau_c = tau_c self.__tau_d = tau_d ts = get_simulator().machine_time_step / 1000.0 self.__tau_c_data = get_exp_lut_array(ts, self.__tau_c, shift=LOOKUP_TAU_C_SHIFT) self.__tau_d_data = get_exp_lut_array(ts, self.__tau_d, shift=LOOKUP_TAU_D_SHIFT) self.__w_min = w_min self.__w_max = w_max if w_min < 0 or w_max < 0: raise SynapticConfigurationException( "Minimum and maximum weights must be >= 0")
def create_machine_vertices(self, resource_tracker, machine_graph): app_vertex = self._governed_app_vertex label = app_vertex.label constraints = get_remaining_constraints(app_vertex) # Structural plasticity can only be run on a single synapse core if (isinstance(app_vertex.synapse_dynamics, AbstractSynapseDynamicsStructural) and self.__n_synapse_vertices != 1): raise SynapticConfigurationException( "The current implementation of structural plasticity can only" " be run on a single synapse core. Please ensure the number" " of synapse cores is set to 1") # Do some checks to make sure everything is likely to fit atoms_per_core = min(app_vertex.get_max_atoms_per_core(), app_vertex.n_atoms) n_synapse_types = app_vertex.neuron_impl.get_n_synapse_types() if (get_n_bits(atoms_per_core) + get_n_bits(n_synapse_types) + get_n_bits(self.__get_max_delay)) > MAX_RING_BUFFER_BITS: raise SynapticConfigurationException( "The combination of the number of neurons per core ({}), " "the number of synapse types ({}), and the maximum delay per " "core ({}) will require too much DTCM. Please reduce one or " "more of these values.".format(atoms_per_core, n_synapse_types, self.__get_max_delay)) self.__neuron_vertices = list() self.__synapse_vertices = list() self.__synapse_verts_by_neuron = defaultdict(list) incoming_direct_poisson = self.__handle_poisson_sources( label, machine_graph) # Work out the ring buffer shifts based on all incoming things rb_shifts = app_vertex.get_ring_buffer_shifts( app_vertex.incoming_projections) weight_scales = app_vertex.get_weight_scales(rb_shifts) # Get resources for synapses independent_synapse_sdram = self.__independent_synapse_sdram() proj_dependent_sdram = self.__proj_dependent_synapse_sdram( app_vertex.incoming_projections) for index, vertex_slice in enumerate(self.__get_fixed_slices()): # Find the maximum number of cores on any chip available max_crs = resource_tracker.get_maximum_cores_available_on_a_chip() if max_crs < (self.__n_synapse_vertices + 1): raise ConfigurationException( "No chips remaining with enough cores for" f" {self.__n_synapse_vertices} synapse cores and a neuron" " core") max_crs -= self.__n_synapse_vertices + 1 # Create the neuron vertex for the slice neuron_vertex, neuron_resources = self.__add_neuron_core( vertex_slice, label, index, rb_shifts, weight_scales, machine_graph, constraints) # Keep track of synapse vertices for each neuron vertex and # resources used by each core (neuron core is added later) synapse_vertices = list() self.__synapse_verts_by_neuron[neuron_vertex] = synapse_vertices all_resources = [] # Add the first vertex synapse_references, syn_label = self.__add_lead_synapse_core( vertex_slice, independent_synapse_sdram, proj_dependent_sdram, label, rb_shifts, weight_scales, all_resources, machine_graph, synapse_vertices, neuron_vertex, constraints) # Do the remaining synapse cores for i in range(1, self.__n_synapse_vertices): self.__add_shared_synapse_core(syn_label, i, vertex_slice, synapse_references, all_resources, machine_graph, synapse_vertices, neuron_vertex, constraints) # Add resources for Poisson vertices up to core limit poisson_vertices = incoming_direct_poisson[vertex_slice] remaining_poisson_vertices = list() added_poisson_vertices = list() for poisson_vertex, poisson_edge in poisson_vertices: if max_crs <= 0: remaining_poisson_vertices.append(poisson_vertex) self.__add_poisson_multicast(poisson_vertex, synapse_vertices, machine_graph, poisson_edge) else: all_resources.append( (poisson_vertex.resources_required, [])) added_poisson_vertices.append(poisson_vertex) max_crs -= 1 if remaining_poisson_vertices: logger.warn( f"Vertex {label} is using multicast for" f" {len(remaining_poisson_vertices)} one-to-one Poisson" " sources as not enough cores exist to put them on the" " same chip") # Create an SDRAM edge partition sdram_label = "SDRAM {} Synapses-->Neurons:{}-{}".format( label, vertex_slice.lo_atom, vertex_slice.hi_atom) source_vertices = added_poisson_vertices + synapse_vertices sdram_partition = SourceSegmentedSDRAMMachinePartition( SYNAPSE_SDRAM_PARTITION_ID, sdram_label, source_vertices) machine_graph.add_outgoing_edge_partition(sdram_partition) neuron_vertex.set_sdram_partition(sdram_partition) # Add SDRAM edges for synapse vertices for source_vertex in source_vertices: edge_label = "SDRAM {}-->{}".format(source_vertex.label, neuron_vertex.label) machine_graph.add_edge( SDRAMMachineEdge(source_vertex, neuron_vertex, edge_label), SYNAPSE_SDRAM_PARTITION_ID) source_vertex.set_sdram_partition(sdram_partition) # Add SDRAM edge requirements to the neuron SDRAM, as the resource # tracker will otherwise try to add another core for it extra_sdram = MultiRegionSDRAM() extra_sdram.merge(neuron_resources.sdram) extra_sdram.add_cost( len(extra_sdram.regions) + 1, sdram_partition.total_sdram_requirements()) neuron_resources_plus = ResourceContainer( sdram=extra_sdram, dtcm=neuron_resources.dtcm, cpu_cycles=neuron_resources.cpu_cycles, iptags=neuron_resources.iptags, reverse_iptags=neuron_resources.reverse_iptags) all_resources.append((neuron_resources_plus, constraints)) # Allocate all the resources to ensure they all fit resource_tracker.allocate_constrained_group_resources( all_resources) return True
def set_delay(self, delay): if delay != 1: raise SynapticConfigurationException( "Neuromodulation delay must be 0")
def sdram_requirement(self, sdram_machine_edge): if isinstance(sdram_machine_edge.pre_vertex, SendsSynapticInputsOverSDRAM): return self.n_bytes_for_transfer raise SynapticConfigurationException( "Unknown pre vertex type in edge {}".format(sdram_machine_edge))