예제 #1
0
class AbstractPopulationVertex(
        ApplicationVertex, AbstractGeneratesDataSpecification,
        AbstractHasAssociatedBinary, AbstractContainsUnits,
        AbstractSpikeRecordable, AbstractNeuronRecordable,
        AbstractProvidesOutgoingPartitionConstraints,
        AbstractProvidesIncomingPartitionConstraints,
        AbstractPopulationInitializable, AbstractPopulationSettable,
        AbstractChangableAfterRun, AbstractHasGlobalMaxAtoms,
        AbstractRewritesDataSpecification, AbstractReadParametersBeforeSet,
        AbstractAcceptsIncomingSynapses, ProvidesKeyToAtomMappingImpl):
    """ Underlying vertex model for Neural Populations.
    """
    __slots__ = [
        "_additional_input", "_binary", "_buffer_size_before_receive",
        "_change_requires_mapping",
        "_change_requires_neuron_parameters_reload",
        "_incoming_spike_buffer_size", "_input_type",
        "_maximum_sdram_for_buffering", "_minimum_buffer_sdram", "_model_name",
        "_n_atoms", "_n_profile_samples", "_neuron_model", "_neuron_recorder",
        "_receive_buffer_host", "_receive_buffer_port", "_spike_recorder",
        "_synapse_manager", "_threshold_type", "_time_between_requests",
        "_units", "_using_auto_pause_and_resume"
    ]

    BASIC_MALLOC_USAGE = 2

    # recording region ids
    SPIKE_RECORDING_REGION = 0
    V_RECORDING_REGION = 1
    GSYN_EXCITATORY_RECORDING_REGION = 2
    GSYN_INHIBITORY_RECORDING_REGION = 3

    RECORDING_REGION = {"spikes": 0, "v": 1, "gsyn_exc": 2, "gsyn_inh": 3}

    VARIABLE_LONG = {
        "spikes": "spikes",
        "v": "membrane voltage",
        "gsyn_exc": "gsyn_excitatory",
        "gsyn_inh": "gsyn_inhibitory"
    }

    N_RECORDING_REGIONS = 4

    # the size of the runtime SDP port data region
    RUNTIME_SDP_PORT_SIZE = 4

    # 6 elements before the start of global parameters
    BYTES_TILL_START_OF_GLOBAL_PARAMETERS = 24

    _n_vertices = 0

    non_pynn_default_parameters = {
        'spikes_per_second': None,
        'ring_buffer_sigma': None,
        'incoming_spike_buffer_size': None,
        'constraints': None,
        'label': None
    }

    def __init__(self,
                 n_neurons,
                 binary,
                 label,
                 max_atoms_per_core,
                 spikes_per_second,
                 ring_buffer_sigma,
                 incoming_spike_buffer_size,
                 model_name,
                 neuron_model,
                 input_type,
                 synapse_type,
                 threshold_type,
                 additional_input=None,
                 constraints=None):
        # pylint: disable=too-many-arguments, too-many-locals
        super(AbstractPopulationVertex, self).__init__(label, constraints,
                                                       max_atoms_per_core)

        self._units = {
            'spikes': 'spikes',
            'v': 'mV',
            'gsyn_exc': "uS",
            'gsyn_inh': "uS"
        }

        self._binary = binary
        self._n_atoms = n_neurons

        # buffer data
        self._incoming_spike_buffer_size = incoming_spike_buffer_size

        # get config from simulator
        config = globals_variables.get_simulator().config

        if incoming_spike_buffer_size is None:
            self._incoming_spike_buffer_size = config.getint(
                "Simulation", "incoming_spike_buffer_size")

        self._model_name = model_name
        self._neuron_model = neuron_model
        self._input_type = input_type
        self._threshold_type = threshold_type
        self._additional_input = additional_input

        # Set up for recording
        self._neuron_recorder = NeuronRecorder(
            ["spikes", "v", "gsyn_exc", "gsyn_inh"], n_neurons)

        self._time_between_requests = config.getint("Buffers",
                                                    "time_between_requests")
        self._minimum_buffer_sdram = config.getint("Buffers",
                                                   "minimum_buffer_sdram")
        self._using_auto_pause_and_resume = config.getboolean(
            "Buffers", "use_auto_pause_and_resume")
        self._receive_buffer_host = config.get("Buffers",
                                               "receive_buffer_host")
        self._receive_buffer_port = helpful_functions.read_config_int(
            config, "Buffers", "receive_buffer_port")

        # If live buffering is enabled, set a maximum on the buffer sizes
        spike_buffer_max_size = 0
        v_buffer_max_size = 0
        gsyn_buffer_max_size = 0
        self._buffer_size_before_receive = None
        if config.getboolean("Buffers", "enable_buffered_recording"):
            spike_buffer_max_size = config.getint("Buffers",
                                                  "spike_buffer_size")
            v_buffer_max_size = config.getint("Buffers", "v_buffer_size")
            gsyn_buffer_max_size = config.getint("Buffers", "gsyn_buffer_size")
            self._buffer_size_before_receive = config.getint(
                "Buffers", "buffer_size_before_receive")

        self._maximum_sdram_for_buffering = [
            spike_buffer_max_size, v_buffer_max_size, gsyn_buffer_max_size,
            gsyn_buffer_max_size
        ]

        # Set up synapse handling
        self._synapse_manager = SynapticManager(synapse_type,
                                                ring_buffer_sigma,
                                                spikes_per_second, config)

        # bool for if state has changed.
        self._change_requires_mapping = True
        self._change_requires_neuron_parameters_reload = False

        # Set up for profiling
        self._n_profile_samples = helpful_functions.read_config_int(
            config, "Reports", "n_profile_samples")

    @property
    @overrides(ApplicationVertex.n_atoms)
    def n_atoms(self):
        return self._n_atoms

    @inject_items({
        "graph": "MemoryApplicationGraph",
        "n_machine_time_steps": "TotalMachineTimeSteps",
        "machine_time_step": "MachineTimeStep"
    })
    @overrides(ApplicationVertex.get_resources_used_by_atoms,
               additional_arguments={
                   "graph", "n_machine_time_steps", "machine_time_step"
               })
    def get_resources_used_by_atoms(self, vertex_slice, graph,
                                    n_machine_time_steps, machine_time_step):
        # pylint: disable=arguments-differ

        # set resources required from this object
        container = ResourceContainer(
            sdram=SDRAMResource(
                self.get_sdram_usage_for_atoms(vertex_slice, graph,
                                               machine_time_step)),
            dtcm=DTCMResource(self.get_dtcm_usage_for_atoms(vertex_slice)),
            cpu_cycles=CPUCyclesPerTickResource(
                self.get_cpu_usage_for_atoms(vertex_slice)))

        recording_sizes = recording_utilities.get_recording_region_sizes(
            self._get_buffered_sdram(vertex_slice, n_machine_time_steps),
            self._minimum_buffer_sdram, self._maximum_sdram_for_buffering,
            self._using_auto_pause_and_resume)
        container.extend(
            recording_utilities.get_recording_resources(
                recording_sizes, self._receive_buffer_host,
                self._receive_buffer_port))

        # return the total resources.
        return container

    @property
    @overrides(AbstractChangableAfterRun.requires_mapping)
    def requires_mapping(self):
        return self._change_requires_mapping

    @overrides(AbstractChangableAfterRun.mark_no_changes)
    def mark_no_changes(self):
        self._change_requires_mapping = False

    def _get_buffered_sdram_per_timestep(self, vertex_slice):
        return [
            self._neuron_recorder.get_buffered_sdram_per_timestep(
                "spikes", vertex_slice),
            self._neuron_recorder.get_buffered_sdram_per_timestep(
                "v", vertex_slice),
            self._neuron_recorder.get_buffered_sdram_per_timestep(
                "gsyn_exc", vertex_slice),
            self._neuron_recorder.get_buffered_sdram_per_timestep(
                "gsyn_inh", vertex_slice)
        ]

    def _get_buffered_sdram(self, vertex_slice, n_machine_time_steps):
        return [
            self._neuron_recorder.get_buffered_sdram("spikes", vertex_slice,
                                                     n_machine_time_steps),
            self._neuron_recorder.get_buffered_sdram("v", vertex_slice,
                                                     n_machine_time_steps),
            self._neuron_recorder.get_buffered_sdram("gsyn_exc", vertex_slice,
                                                     n_machine_time_steps),
            self._neuron_recorder.get_buffered_sdram("gsyn_inh", vertex_slice,
                                                     n_machine_time_steps)
        ]

    @inject_items({"n_machine_time_steps": "TotalMachineTimeSteps"})
    @overrides(ApplicationVertex.create_machine_vertex,
               additional_arguments={"n_machine_time_steps"})
    def create_machine_vertex(self,
                              vertex_slice,
                              resources_required,
                              n_machine_time_steps,
                              label=None,
                              constraints=None):
        # pylint: disable=too-many-arguments, arguments-differ
        is_recording = len(self._neuron_recorder.recording_variables) > 0
        buffered_sdram_per_timestep = self._get_buffered_sdram_per_timestep(
            vertex_slice)
        buffered_sdram = self._get_buffered_sdram(vertex_slice,
                                                  n_machine_time_steps)
        minimum_buffer_sdram = recording_utilities.get_minimum_buffer_sdram(
            buffered_sdram, self._minimum_buffer_sdram)
        overflow_sdram = self._neuron_recorder.get_sampling_overflow_sdram(
            vertex_slice)
        vertex = PopulationMachineVertex(resources_required, is_recording,
                                         minimum_buffer_sdram,
                                         buffered_sdram_per_timestep, label,
                                         constraints, overflow_sdram)

        AbstractPopulationVertex._n_vertices += 1

        # return machine vertex
        return vertex

    def get_cpu_usage_for_atoms(self, vertex_slice):
        per_neuron_cycles = (
            _NEURON_BASE_N_CPU_CYCLES_PER_NEURON +
            self._neuron_model.get_n_cpu_cycles_per_neuron() +
            self._input_type.get_n_cpu_cycles_per_neuron(
                self._synapse_manager.synapse_type.get_n_synapse_types()) +
            self._threshold_type.get_n_cpu_cycles_per_neuron())
        if self._additional_input is not None:
            per_neuron_cycles += \
                self._additional_input.get_n_cpu_cycles_per_neuron()
        return (_NEURON_BASE_N_CPU_CYCLES + _C_MAIN_BASE_N_CPU_CYCLES +
                (per_neuron_cycles * vertex_slice.n_atoms) +
                self._neuron_recorder.get_n_cpu_cycles(vertex_slice.n_atoms) +
                self._synapse_manager.get_n_cpu_cycles())

    def get_dtcm_usage_for_atoms(self, vertex_slice):
        per_neuron_usage = (
            self._neuron_model.get_dtcm_usage_per_neuron_in_bytes() +
            self._input_type.get_dtcm_usage_per_neuron_in_bytes() +
            self._threshold_type.get_dtcm_usage_per_neuron_in_bytes())
        if self._additional_input is not None:
            per_neuron_usage += \
                self._additional_input.get_dtcm_usage_per_neuron_in_bytes()
        return (_NEURON_BASE_DTCM_USAGE_IN_BYTES +
                (per_neuron_usage * vertex_slice.n_atoms) +
                self._neuron_recorder.get_dtcm_usage_in_bytes(vertex_slice) +
                self._synapse_manager.get_dtcm_usage_in_bytes())

    def _get_sdram_usage_for_neuron_params_per_neuron(self):
        per_neuron_usage = (
            self._input_type.get_sdram_usage_per_neuron_in_bytes() +
            self._threshold_type.get_sdram_usage_per_neuron_in_bytes() +
            self._neuron_recorder.get_sdram_usage_per_neuron_in_bytes() +
            self._neuron_model.get_sdram_usage_per_neuron_in_bytes())
        if self._additional_input is not None:
            per_neuron_usage += \
                self._additional_input.get_sdram_usage_per_neuron_in_bytes()
        return per_neuron_usage

    def _get_sdram_usage_for_neuron_params(self, vertex_slice):
        """ calculates the sdram usage for just the neuron parameters region

        :param vertex_slice: the slice of atoms.
        :return:  The sdram required for the neuron region
        """
        per_neuron_usage = \
            self._get_sdram_usage_for_neuron_params_per_neuron()
        return (self.BYTES_TILL_START_OF_GLOBAL_PARAMETERS +
                self._neuron_model.
                get_sdram_usage_for_global_parameters_in_bytes() +
                self._neuron_recorder.
                get_sdram_usage_for_global_parameters_in_bytes() +
                (per_neuron_usage * vertex_slice.n_atoms))

    def get_sdram_usage_for_atoms(self, vertex_slice, graph,
                                  machine_time_step):
        sdram_requirement = (
            common_constants.SYSTEM_BYTES_REQUIREMENT +
            self._get_sdram_usage_for_neuron_params(vertex_slice) +
            recording_utilities.get_recording_header_size(
                self.N_RECORDING_REGIONS) +
            PopulationMachineVertex.get_provenance_data_size(
                PopulationMachineVertex.N_ADDITIONAL_PROVENANCE_DATA_ITEMS) +
            self._synapse_manager.get_sdram_usage_in_bytes(
                vertex_slice, graph.get_edges_ending_at_vertex(self),
                machine_time_step) +
            (self._get_number_of_mallocs_used_by_dsg() *
             common_constants.SARK_PER_MALLOC_SDRAM_USAGE) +
            profile_utils.get_profile_region_size(self._n_profile_samples))

        return sdram_requirement

    def _get_number_of_mallocs_used_by_dsg(self):
        extra_mallocs = len(self._neuron_recorder.recording_variables)
        return (self.BASIC_MALLOC_USAGE +
                self._synapse_manager.get_number_of_mallocs_used_by_dsg() +
                extra_mallocs)

    def _reserve_memory_regions(self, spec, vertex_slice, vertex):

        spec.comment("\nReserving memory space for data regions:\n\n")

        # Reserve memory:
        spec.reserve_memory_region(
            region=constants.POPULATION_BASED_REGIONS.SYSTEM.value,
            size=common_constants.SYSTEM_BYTES_REQUIREMENT,
            label='System')

        self._reserve_neuron_params_data_region(spec, vertex_slice)

        spec.reserve_memory_region(
            region=constants.POPULATION_BASED_REGIONS.RECORDING.value,
            size=recording_utilities.get_recording_header_size(
                self.N_RECORDING_REGIONS))

        profile_utils.reserve_profile_region(
            spec, constants.POPULATION_BASED_REGIONS.PROFILING.value,
            self._n_profile_samples)

        vertex.reserve_provenance_data_region(spec)

    def _reserve_neuron_params_data_region(self, spec, vertex_slice):
        """ reserve the neuron parameter data region

        :param spec: the spec to write the dsg region to
        :param vertex_slice: the slice of atoms from the application vertex
        :return: None
        """
        params_size = self._get_sdram_usage_for_neuron_params(vertex_slice)
        spec.reserve_memory_region(
            region=constants.POPULATION_BASED_REGIONS.NEURON_PARAMS.value,
            size=params_size,
            label='NeuronParams')

    def _write_neuron_parameters(self, spec, key, vertex_slice,
                                 machine_time_step, time_scale_factor):
        # pylint: disable=too-many-arguments
        n_atoms = (vertex_slice.hi_atom - vertex_slice.lo_atom) + 1
        spec.comment(
            "\nWriting Neuron Parameters for {} Neurons:\n".format(n_atoms))

        # Set the focus to the memory region 2 (neuron parameters):
        spec.switch_write_focus(
            region=constants.POPULATION_BASED_REGIONS.NEURON_PARAMS.value)

        # Write the random back off value
        spec.write_value(
            random.randint(0, AbstractPopulationVertex._n_vertices))

        # Write the number of microseconds between sending spikes
        time_between_spikes = ((machine_time_step * time_scale_factor) /
                               (n_atoms * 2.0))
        spec.write_value(data=int(time_between_spikes))

        # Write whether the key is to be used, and then the key, or 0 if it
        # isn't to be used
        if key is None:
            spec.write_value(data=0)
            spec.write_value(data=0)
        else:
            spec.write_value(data=1)
            spec.write_value(data=key)

        # Write the number of neurons in the block:
        spec.write_value(data=n_atoms)

        # Write the size of the incoming spike buffer
        spec.write_value(data=self._incoming_spike_buffer_size)

        # Write the recording rates and sizes
        record_globals = self._neuron_recorder.get_global_parameters(
            vertex_slice)
        for param in record_globals:
            spec.write_value(data=param.get_value(),
                             data_type=param.get_dataspec_datatype())

        # Write the index parameters
        indexes = self._neuron_recorder.get_index_parameters(vertex_slice)
        utility_calls.write_parameters_per_neuron(spec,
                                                  vertex_slice,
                                                  indexes,
                                                  slice_paramaters=True)

        # Write the global parameters
        global_params = self._neuron_model.get_global_parameters()
        for param in global_params:
            spec.write_value(data=param.get_value(),
                             data_type=param.get_dataspec_datatype())

        # Write the neuron parameters
        utility_calls.write_parameters_per_neuron(
            spec, vertex_slice, self._neuron_model.get_neural_parameters())

        # Write the input type parameters
        utility_calls.write_parameters_per_neuron(
            spec, vertex_slice, self._input_type.get_input_type_parameters())

        # Write the additional input parameters
        if self._additional_input is not None:
            utility_calls.write_parameters_per_neuron(
                spec, vertex_slice, self._additional_input.get_parameters())

        # Write the threshold type parameters
        utility_calls.write_parameters_per_neuron(
            spec, vertex_slice,
            self._threshold_type.get_threshold_parameters())

    @inject_items({
        "machine_time_step": "MachineTimeStep",
        "time_scale_factor": "TimeScaleFactor",
        "graph_mapper": "MemoryGraphMapper",
        "routing_info": "MemoryRoutingInfos"
    })
    @overrides(AbstractRewritesDataSpecification.regenerate_data_specification,
               additional_arguments={
                   "machine_time_step", "time_scale_factor", "graph_mapper",
                   "routing_info"
               })
    def regenerate_data_specification(self, spec, placement, machine_time_step,
                                      time_scale_factor, graph_mapper,
                                      routing_info):
        # pylint: disable=too-many-arguments, arguments-differ
        vertex_slice = graph_mapper.get_slice(placement.vertex)

        # reserve the neuron parameters data region
        self._reserve_neuron_params_data_region(
            spec, graph_mapper.get_slice(placement.vertex))

        # write the neuron params into the new dsg region
        self._write_neuron_parameters(
            key=routing_info.get_first_key_from_pre_vertex(
                placement.vertex, constants.SPIKE_PARTITION_ID),
            machine_time_step=machine_time_step,
            spec=spec,
            time_scale_factor=time_scale_factor,
            vertex_slice=vertex_slice)

        self._synapse_manager.regenerate_data_specification(
            spec, placement, machine_time_step, time_scale_factor,
            vertex_slice)

        # close spec
        spec.end_specification()

    @overrides(AbstractRewritesDataSpecification.
               requires_memory_regions_to_be_reloaded)
    def requires_memory_regions_to_be_reloaded(self):
        return self._change_requires_neuron_parameters_reload

    @overrides(AbstractRewritesDataSpecification.mark_regions_reloaded)
    def mark_regions_reloaded(self):
        self._change_requires_neuron_parameters_reload = False

    @inject_items({
        "machine_time_step": "MachineTimeStep",
        "time_scale_factor": "TimeScaleFactor",
        "graph_mapper": "MemoryGraphMapper",
        "application_graph": "MemoryApplicationGraph",
        "machine_graph": "MemoryMachineGraph",
        "routing_info": "MemoryRoutingInfos",
        "tags": "MemoryTags",
        "n_machine_time_steps": "TotalMachineTimeSteps"
    })
    @overrides(AbstractGeneratesDataSpecification.generate_data_specification,
               additional_arguments={
                   "machine_time_step", "time_scale_factor", "graph_mapper",
                   "application_graph", "machine_graph", "routing_info",
                   "tags", "n_machine_time_steps"
               })
    def generate_data_specification(self, spec, placement, machine_time_step,
                                    time_scale_factor, graph_mapper,
                                    application_graph, machine_graph,
                                    routing_info, tags, n_machine_time_steps):
        # pylint: disable=too-many-arguments, arguments-differ
        vertex = placement.vertex

        spec.comment("\n*** Spec for block of {} neurons ***\n".format(
            self._model_name))
        vertex_slice = graph_mapper.get_slice(vertex)

        # Reserve memory regions
        self._reserve_memory_regions(spec, vertex_slice, vertex)

        # Declare random number generators and distributions:
        # TODO add random distribution stuff
        # self.write_random_distribution_declarations(spec)

        # Get the key
        key = routing_info.get_first_key_from_pre_vertex(
            vertex, constants.SPIKE_PARTITION_ID)

        # Write the setup region
        spec.switch_write_focus(
            constants.POPULATION_BASED_REGIONS.SYSTEM.value)
        spec.write_array(
            simulation_utilities.get_simulation_header_array(
                self.get_binary_file_name(), machine_time_step,
                time_scale_factor))

        # Write the recording region
        spec.switch_write_focus(
            constants.POPULATION_BASED_REGIONS.RECORDING.value)
        ip_tags = tags.get_ip_tags_for_vertex(vertex)
        recorded_region_sizes = recording_utilities.get_recorded_region_sizes(
            self._get_buffered_sdram(vertex_slice, n_machine_time_steps),
            self._maximum_sdram_for_buffering)
        spec.write_array(
            recording_utilities.get_recording_header_array(
                recorded_region_sizes, self._time_between_requests,
                self._buffer_size_before_receive, ip_tags))

        # Write the neuron parameters
        self._write_neuron_parameters(spec, key, vertex_slice,
                                      machine_time_step, time_scale_factor)

        # write profile data
        profile_utils.write_profile_region_data(
            spec, constants.POPULATION_BASED_REGIONS.PROFILING.value,
            self._n_profile_samples)

        # allow the synaptic matrix to write its data spec-able data
        self._synapse_manager.write_data_spec(spec, self, vertex_slice, vertex,
                                              placement, machine_graph,
                                              application_graph, routing_info,
                                              graph_mapper, self._input_type,
                                              machine_time_step)

        # End the writing of this specification:
        spec.end_specification()

    @overrides(AbstractHasAssociatedBinary.get_binary_file_name)
    def get_binary_file_name(self):

        # Split binary name into title and extension
        binary_title, binary_extension = os.path.splitext(self._binary)

        # Reunite title and extension and return
        return (binary_title + self._synapse_manager.vertex_executable_suffix +
                binary_extension)

    @overrides(AbstractHasAssociatedBinary.get_binary_start_type)
    def get_binary_start_type(self):
        return ExecutableType.USES_SIMULATION_INTERFACE

    @overrides(AbstractSpikeRecordable.is_recording_spikes)
    def is_recording_spikes(self):
        return self._neuron_recorder.is_recording("spikes")

    @overrides(AbstractSpikeRecordable.set_recording_spikes)
    def set_recording_spikes(self,
                             new_state=True,
                             sampling_interval=None,
                             indexes=None):
        self.set_recording("spikes", new_state, sampling_interval, indexes)

    @overrides(AbstractSpikeRecordable.get_spikes)
    def get_spikes(self, placements, graph_mapper, buffer_manager,
                   machine_time_step):
        return self._neuron_recorder.get_spikes(self.label, buffer_manager,
                                                self.SPIKE_RECORDING_REGION,
                                                placements, graph_mapper, self,
                                                machine_time_step)

    @overrides(AbstractNeuronRecordable.get_recordable_variables)
    def get_recordable_variables(self):
        return self._neuron_recorder.get_recordable_variables()

    @overrides(AbstractNeuronRecordable.is_recording)
    def is_recording(self, variable):
        return self._neuron_recorder.is_recording(variable)

    @overrides(AbstractNeuronRecordable.set_recording)
    def set_recording(self,
                      variable,
                      new_state=True,
                      sampling_interval=None,
                      indexes=None):
        self._change_requires_mapping = not self.is_recording(variable)
        self._neuron_recorder.set_recording(variable, new_state,
                                            sampling_interval, indexes)

    @overrides(AbstractNeuronRecordable.get_data)
    def get_data(self, variable, n_machine_time_steps, placements,
                 graph_mapper, buffer_manager, machine_time_step):
        # pylint: disable=too-many-arguments
        return self._neuron_recorder.get_matrix_data(
            self.label, buffer_manager, self.RECORDING_REGION[variable],
            placements, graph_mapper, self, variable, n_machine_time_steps)

    @overrides(AbstractNeuronRecordable.get_neuron_sampling_interval)
    def get_neuron_sampling_interval(self, variable):
        return self._neuron_recorder.get_neuron_sampling_interval(variable)

    @overrides(AbstractSpikeRecordable.get_spikes_sampling_interval)
    def get_spikes_sampling_interval(self):
        return self._neuron_recorder.get_neuron_sampling_interval("spikes")

    @overrides(AbstractPopulationInitializable.initialize)
    def initialize(self, variable, value):
        initialize_attr = getattr(self._neuron_model,
                                  "initialize_%s" % variable, None)
        if initialize_attr is None or not callable(initialize_attr):
            raise Exception("Vertex does not support initialisation of"
                            " parameter {}".format(variable))
        initialize_attr(value)
        self._change_requires_neuron_parameters_reload = True

    @property
    def input_type(self):
        return self._input_type

    @overrides(AbstractPopulationSettable.get_value)
    def get_value(self, key):
        """ Get a property of the overall model
        """
        for obj in [
                self._neuron_model, self._input_type, self._threshold_type,
                self._synapse_manager.synapse_type, self._additional_input,
                self
        ]:
            if hasattr(obj, key):
                return getattr(obj, key)
        raise Exception("Population {} does not have parameter {}".format(
            self._model_name, key))

    @overrides(AbstractPopulationSettable.set_value)
    def set_value(self, key, value):
        """ Set a property of the overall model
        """
        for obj in [
                self._neuron_model, self._input_type, self._threshold_type,
                self._synapse_manager.synapse_type, self._additional_input
        ]:
            if hasattr(obj, key):
                setattr(obj, key, value)
                self._change_requires_neuron_parameters_reload = True
                return
        raise InvalidParameterType("Type {} does not have parameter {}".format(
            type(self), key))

    @overrides(AbstractReadParametersBeforeSet.read_parameters_from_machine)
    def read_parameters_from_machine(self, transceiver, placement,
                                     vertex_slice):

        # locate sdram address to where the neuron parameters are stored
        neuron_region_sdram_address = \
            helpful_functions.locate_memory_region_for_placement(
                placement,
                constants.POPULATION_BASED_REGIONS.NEURON_PARAMS.value,
                transceiver)

        # shift past the extra stuff before neuron parameters that we don't
        # need to read
        neuron_parameters_sdram_address = (
            neuron_region_sdram_address +
            self.BYTES_TILL_START_OF_GLOBAL_PARAMETERS)

        # get size of neuron params
        size_of_region = self._get_sdram_usage_for_neuron_params(vertex_slice)
        size_of_region -= self.BYTES_TILL_START_OF_GLOBAL_PARAMETERS

        # get data from the machine
        byte_array = transceiver.read_memory(placement.x, placement.y,
                                             neuron_parameters_sdram_address,
                                             size_of_region)

        # Skip the recorder globals as these are not change on machione
        # Just written out in case data is changed and written back
        offset = self._neuron_recorder.get_size_of_global_parameters(
            vertex_slice)

        # update python neuron parameters with the data

        # handle global params (only once, so given a slice of 0 to 0)
        global_params, offset = utility_calls.translate_parameters(
            self._neuron_model.get_global_parameter_types(), byte_array,
            offset, Slice(0, 0))
        self._neuron_model.set_global_parameters(global_params)

        # handle state params for a neuron
        neuron_params, offset = utility_calls.translate_parameters(
            self._neuron_model.get_neural_parameter_types(), byte_array,
            offset, vertex_slice)
        self._neuron_model.set_neural_parameters(neuron_params, vertex_slice)

        # handle input params
        input_params, offset = utility_calls.translate_parameters(
            self._input_type.get_input_type_parameter_types(), byte_array,
            offset, vertex_slice)
        self._input_type.set_input_type_parameters(input_params, vertex_slice)

        # handle additional input params, if they exist
        if self._additional_input is not None:
            additional_params, offset = utility_calls.translate_parameters(
                self._additional_input.get_parameter_types(), byte_array,
                offset, vertex_slice)
            self._additional_input.set_parameters(additional_params,
                                                  vertex_slice)

        # handle threshold type params
        threshold_params, offset = utility_calls.translate_parameters(
            self._threshold_type.get_threshold_parameter_types(), byte_array,
            offset, vertex_slice)
        self._threshold_type.set_threshold_parameters(threshold_params,
                                                      vertex_slice)

        # Read synapse parameters
        self._synapse_manager.read_parameters_from_machine(
            transceiver, placement, vertex_slice)

    @property
    def weight_scale(self):
        return self._input_type.get_global_weight_scale()

    @property
    def ring_buffer_sigma(self):
        return self._synapse_manager.ring_buffer_sigma

    @ring_buffer_sigma.setter
    def ring_buffer_sigma(self, ring_buffer_sigma):
        self._synapse_manager.ring_buffer_sigma = ring_buffer_sigma

    @property
    def spikes_per_second(self):
        return self._synapse_manager.spikes_per_second

    @spikes_per_second.setter
    def spikes_per_second(self, spikes_per_second):
        self._synapse_manager.spikes_per_second = spikes_per_second

    @property
    def synapse_dynamics(self):
        return self._synapse_manager.synapse_dynamics

    def set_synapse_dynamics(self, synapse_dynamics):
        self._synapse_manager.synapse_dynamics = synapse_dynamics

    def add_pre_run_connection_holder(self, connection_holder, edge,
                                      synapse_info):
        # pylint: disable=arguments-differ
        self._synapse_manager.add_pre_run_connection_holder(
            connection_holder, edge, synapse_info)

    @overrides(AbstractAcceptsIncomingSynapses.get_connections_from_machine)
    def get_connections_from_machine(
            self,
            transceiver,
            placement,
            edge,
            graph_mapper,
            routing_infos,
            synapse_information,
            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):
        # pylint: disable=too-many-arguments
        return self._synapse_manager.get_connections_from_machine(
            transceiver, placement, edge, graph_mapper, routing_infos,
            synapse_information, machine_time_step, using_extra_monitor_cores,
            placements, data_receiver, sender_extra_monitor_core_placement,
            extra_monitor_cores_for_router_timeout,
            handle_time_out_configuration, fixed_routes)

    def clear_connection_cache(self):
        self._synapse_manager.clear_connection_cache()

    @property
    def synapse_type(self):
        return self._synapse_manager.synapse_type

    def get_maximum_delay_supported_in_ms(self, machine_time_step):
        return self._synapse_manager.get_maximum_delay_supported_in_ms(
            machine_time_step)

    @overrides(AbstractProvidesIncomingPartitionConstraints.
               get_incoming_partition_constraints)
    def get_incoming_partition_constraints(self, partition):
        """ Gets the constraints for partitions going into this vertex

        :param partition: partition that goes into this vertex
        :return: list of constraints
        """
        return self._synapse_manager.get_incoming_partition_constraints()

    @overrides(AbstractProvidesOutgoingPartitionConstraints.
               get_outgoing_partition_constraints)
    def get_outgoing_partition_constraints(self, partition):
        """ Gets the constraints for partitions going out of this vertex

        :param partition: the partition that leaves this vertex
        :return: list of constraints
        """
        return [ContiguousKeyRangeContraint()]

    @overrides(AbstractNeuronRecordable.clear_recording)
    def clear_recording(self, variable, buffer_manager, placements,
                        graph_mapper):
        self._clear_recording_region(buffer_manager, placements, graph_mapper,
                                     self.RECORDING_REGION[variable])

    @overrides(AbstractSpikeRecordable.clear_spike_recording)
    def clear_spike_recording(self, buffer_manager, placements, graph_mapper):
        self._clear_recording_region(
            buffer_manager, placements, graph_mapper,
            AbstractPopulationVertex.SPIKE_RECORDING_REGION)

    def _clear_recording_region(self, buffer_manager, placements, graph_mapper,
                                recording_region_id):
        """ clears a recorded data region from the buffer manager

        :param buffer_manager: the buffer manager object
        :param placements: the placements object
        :param graph_mapper: the graph mapper object
        :param recording_region_id: the recorded region id for clearing
        :rtype: None
        """
        machine_vertices = graph_mapper.get_machine_vertices(self)
        for machine_vertex in machine_vertices:
            placement = placements.get_placement_of_vertex(machine_vertex)
            buffer_manager.clear_recorded_data(placement.x, placement.y,
                                               placement.p,
                                               recording_region_id)

    @overrides(AbstractContainsUnits.get_units)
    def get_units(self, variable):
        # search the model components for the variable
        for obj in [
                self._neuron_model, self._input_type, self._threshold_type,
                self._synapse_manager.synapse_type, self._additional_input
        ]:
            if (hasattr(obj, variable)
                    and isinstance(obj, AbstractContainsUnits)):
                return obj.unit(variable)
        # if not in the components, must be within myself, so call my own units
        if variable in self._units:
            return self._units[variable]
        else:
            raise InvalidParameterType(
                "The parameter {} does not exist in this input "
                "conductance component".format(variable))

    def describe(self):
        """
        Returns a human-readable description of the cell or synapse type.

        The output may be customised by specifying a different template
        together with an associated template engine
        (see ``pyNN.descriptions``).

        If template is None, then a dictionary containing the template context
        will be returned.
        """
        parameters = dict()
        for parameter_name in self.default_parameters:
            parameters[parameter_name] = self.get_value(parameter_name)

        context = {
            "name": self._model_name,
            "default_parameters": self.default_parameters,
            "default_initial_values": self.default_parameters,
            "parameters": parameters,
        }
        return context

    def __str__(self):
        return "{} with {} atoms".format(self.label, self.n_atoms)

    def __repr__(self):
        return self.__str__()
예제 #2
0
class AbstractPopulationVertex(
        ApplicationVertex, AbstractGeneratesDataSpecification,
        AbstractHasAssociatedBinary, AbstractContainsUnits,
        AbstractSpikeRecordable, AbstractNeuronRecordable,
        AbstractProvidesOutgoingPartitionConstraints,
        AbstractProvidesIncomingPartitionConstraints,
        AbstractPopulationInitializable, AbstractPopulationSettable,
        AbstractChangableAfterRun, AbstractRewritesDataSpecification,
        AbstractReadParametersBeforeSet, AbstractAcceptsIncomingSynapses,
        ProvidesKeyToAtomMappingImpl, AbstractCanReset):
    """ Underlying vertex model for Neural Populations.
    """
    __slots__ = [
        "__change_requires_mapping",
        "__change_requires_neuron_parameters_reload",
        "__change_requires_data_generation",
        "__incoming_spike_buffer_size",
        "__n_atoms",
        "__n_profile_samples",
        "__neuron_impl",
        "__neuron_recorder",
        "_parameters",  # See AbstractPyNNModel
        "__pynn_model",
        "_state_variables",  # See AbstractPyNNModel
        "__synapse_manager",
        "__time_between_requests",
        "__units",
        "__n_subvertices",
        "__n_data_specs",
        "__initial_state_variables",
        "__has_reset_last",
        "__updated_state_variables"
    ]

    BASIC_MALLOC_USAGE = 2

    # recording region IDs
    SPIKE_RECORDING_REGION = 0

    # the size of the runtime SDP port data region
    RUNTIME_SDP_PORT_SIZE = 4

    # 8 elements before the start of global parameters
    BYTES_TILL_START_OF_GLOBAL_PARAMETERS = 32

    # The Buffer traffic type
    TRAFFIC_IDENTIFIER = "BufferTraffic"

    _n_vertices = 0

    def __init__(self, n_neurons, label, constraints, max_atoms_per_core,
                 spikes_per_second, ring_buffer_sigma,
                 incoming_spike_buffer_size, neuron_impl, pynn_model):
        # pylint: disable=too-many-arguments, too-many-locals
        super(AbstractPopulationVertex, self).__init__(label, constraints,
                                                       max_atoms_per_core)

        self.__n_atoms = n_neurons
        self.__n_subvertices = 0
        self.__n_data_specs = 0

        # buffer data
        self.__incoming_spike_buffer_size = incoming_spike_buffer_size

        # get config from simulator
        config = globals_variables.get_simulator().config

        if incoming_spike_buffer_size is None:
            self.__incoming_spike_buffer_size = config.getint(
                "Simulation", "incoming_spike_buffer_size")

        self.__neuron_impl = neuron_impl
        self.__pynn_model = pynn_model
        self._parameters = SpynnakerRangeDictionary(n_neurons)
        self._state_variables = SpynnakerRangeDictionary(n_neurons)
        self.__neuron_impl.add_parameters(self._parameters)
        self.__neuron_impl.add_state_variables(self._state_variables)
        self.__initial_state_variables = None
        self.__updated_state_variables = set()

        # Set up for recording
        recordables = ["spikes"]
        recordables.extend(self.__neuron_impl.get_recordable_variables())
        self.__neuron_recorder = NeuronRecorder(recordables, n_neurons)

        # Set up synapse handling
        self.__synapse_manager = SynapticManager(
            self.__neuron_impl.get_n_synapse_types(), ring_buffer_sigma,
            spikes_per_second, config)

        # bool for if state has changed.
        self.__change_requires_mapping = True
        self.__change_requires_neuron_parameters_reload = False
        self.__change_requires_data_generation = False
        self.__has_reset_last = True

        # Set up for profiling
        self.__n_profile_samples = helpful_functions.read_config_int(
            config, "Reports", "n_profile_samples")

    @property
    @overrides(ApplicationVertex.n_atoms)
    def n_atoms(self):
        return self.__n_atoms

    @property
    def _neuron_recorder(self):  # for testing only
        return self.__neuron_recorder

    @inject_items({
        "graph": "MemoryApplicationGraph",
        "machine_time_step": "MachineTimeStep"
    })
    @overrides(ApplicationVertex.get_resources_used_by_atoms,
               additional_arguments={"graph", "machine_time_step"})
    def get_resources_used_by_atoms(self, vertex_slice, graph,
                                    machine_time_step):
        # pylint: disable=arguments-differ

        variableSDRAM = self.__neuron_recorder.get_variable_sdram_usage(
            vertex_slice)
        constantSDRAM = ConstantSDRAM(
            self._get_sdram_usage_for_atoms(vertex_slice, graph,
                                            machine_time_step))

        # set resources required from this object
        container = ResourceContainer(
            sdram=variableSDRAM + constantSDRAM,
            dtcm=DTCMResource(self.get_dtcm_usage_for_atoms(vertex_slice)),
            cpu_cycles=CPUCyclesPerTickResource(
                self.get_cpu_usage_for_atoms(vertex_slice)))

        # return the total resources.
        return container

    @property
    @overrides(AbstractChangableAfterRun.requires_mapping)
    def requires_mapping(self):
        return self.__change_requires_mapping

    @property
    @overrides(AbstractChangableAfterRun.requires_data_generation)
    def requires_data_generation(self):
        return self.__change_requires_data_generation

    @overrides(AbstractChangableAfterRun.mark_no_changes)
    def mark_no_changes(self):
        self.__change_requires_mapping = False
        self.__change_requires_data_generation = False

    # CB: May be dead code
    def _get_buffered_sdram_per_timestep(self, vertex_slice):
        values = [
            self.__neuron_recorder.get_buffered_sdram_per_timestep(
                "spikes", vertex_slice)
        ]
        for variable in self.__neuron_impl.get_recordable_variables():
            values.append(
                self.__neuron_recorder.get_buffered_sdram_per_timestep(
                    variable, vertex_slice))
        return values

    def _get_buffered_sdram(self, vertex_slice, n_machine_time_steps):
        values = [
            self.__neuron_recorder.get_buffered_sdram("spikes", vertex_slice,
                                                      n_machine_time_steps)
        ]
        for variable in self.__neuron_impl.get_recordable_variables():
            values.append(
                self.__neuron_recorder.get_buffered_sdram(
                    variable, vertex_slice, n_machine_time_steps))
        return values

    @overrides(ApplicationVertex.create_machine_vertex)
    def create_machine_vertex(self,
                              vertex_slice,
                              resources_required,
                              label=None,
                              constraints=None):

        self.__n_subvertices += 1
        return PopulationMachineVertex(
            resources_required, self.__neuron_recorder.recorded_region_ids,
            label, constraints)

    def get_cpu_usage_for_atoms(self, vertex_slice):
        return (_NEURON_BASE_N_CPU_CYCLES + _C_MAIN_BASE_N_CPU_CYCLES +
                (_NEURON_BASE_N_CPU_CYCLES_PER_NEURON * vertex_slice.n_atoms) +
                self.__neuron_recorder.get_n_cpu_cycles(vertex_slice.n_atoms) +
                self.__neuron_impl.get_n_cpu_cycles(vertex_slice.n_atoms) +
                self.__synapse_manager.get_n_cpu_cycles())

    def get_dtcm_usage_for_atoms(self, vertex_slice):
        return (
            _NEURON_BASE_DTCM_USAGE_IN_BYTES +
            self.__neuron_impl.get_dtcm_usage_in_bytes(vertex_slice.n_atoms) +
            self.__neuron_recorder.get_dtcm_usage_in_bytes(vertex_slice) +
            self.__synapse_manager.get_dtcm_usage_in_bytes())

    def _get_sdram_usage_for_neuron_params(self, vertex_slice):
        """ Calculate the SDRAM usage for just the neuron parameters region.

        :param vertex_slice: the slice of atoms.
        :return: The SDRAM required for the neuron region
        """
        return (
            self.BYTES_TILL_START_OF_GLOBAL_PARAMETERS +
            self.__neuron_recorder.get_sdram_usage_in_bytes(vertex_slice) +
            self.__neuron_impl.get_sdram_usage_in_bytes(vertex_slice.n_atoms))

    def _get_sdram_usage_for_atoms(self, vertex_slice, graph,
                                   machine_time_step):
        n_record = len(self.__neuron_impl.get_recordable_variables()) + 1
        sdram_requirement = (
            common_constants.SYSTEM_BYTES_REQUIREMENT +
            self._get_sdram_usage_for_neuron_params(vertex_slice) +
            recording_utilities.get_recording_header_size(n_record) +
            recording_utilities.get_recording_data_constant_size(n_record) +
            PopulationMachineVertex.get_provenance_data_size(
                PopulationMachineVertex.N_ADDITIONAL_PROVENANCE_DATA_ITEMS) +
            self.__synapse_manager.get_sdram_usage_in_bytes(
                vertex_slice, graph.get_edges_ending_at_vertex(self),
                machine_time_step) +
            profile_utils.get_profile_region_size(self.__n_profile_samples))

        return sdram_requirement

    def _reserve_memory_regions(self, spec, vertex_slice, vertex):

        spec.comment("\nReserving memory space for data regions:\n\n")

        # Reserve memory:
        spec.reserve_memory_region(
            region=constants.POPULATION_BASED_REGIONS.SYSTEM.value,
            size=common_constants.SIMULATION_N_BYTES,
            label='System')

        self._reserve_neuron_params_data_region(spec, vertex_slice)

        spec.reserve_memory_region(
            region=constants.POPULATION_BASED_REGIONS.RECORDING.value,
            size=recording_utilities.get_recording_header_size(
                len(self.__neuron_impl.get_recordable_variables()) + 1))

        profile_utils.reserve_profile_region(
            spec, constants.POPULATION_BASED_REGIONS.PROFILING.value,
            self.__n_profile_samples)

        vertex.reserve_provenance_data_region(spec)

    def _reserve_neuron_params_data_region(self, spec, vertex_slice):
        """ Reserve the neuron parameter data region.

        :param spec: the spec to write the DSG region to
        :param vertex_slice: the slice of atoms from the application vertex
        :return: None
        """
        params_size = self._get_sdram_usage_for_neuron_params(vertex_slice)
        spec.reserve_memory_region(
            region=constants.POPULATION_BASED_REGIONS.NEURON_PARAMS.value,
            size=params_size,
            label='NeuronParams')

    @staticmethod
    def __copy_ranged_dict(source, merge=None, merge_keys=None):
        target = SpynnakerRangeDictionary(len(source))
        for key in source.keys():
            copy_list = SpynnakerRangedList(len(source))
            if merge_keys is None or key not in merge_keys:
                init_list = source.get_list(key)
            else:
                init_list = merge.get_list(key)
            for start, stop, value in init_list.iter_ranges():
                is_list = (hasattr(value, '__iter__')
                           and not isinstance(value, str))
                copy_list.set_value_by_slice(start, stop, value, is_list)
            target[key] = copy_list
        return target

    def _write_neuron_parameters(self, spec, key, vertex_slice,
                                 machine_time_step, time_scale_factor):

        # If resetting, reset any state variables that need to be reset
        if (self.__has_reset_last
                and self.__initial_state_variables is not None):
            self._state_variables = self.__copy_ranged_dict(
                self.__initial_state_variables, self._state_variables,
                self.__updated_state_variables)
            self.__initial_state_variables = None

        # If no initial state variables, copy them now
        if self.__has_reset_last:
            self.__initial_state_variables = self.__copy_ranged_dict(
                self._state_variables)

        # Reset things that need resetting
        self.__has_reset_last = False
        self.__updated_state_variables.clear()

        # pylint: disable=too-many-arguments
        n_atoms = vertex_slice.n_atoms
        spec.comment(
            "\nWriting Neuron Parameters for {} Neurons:\n".format(n_atoms))

        # Set the focus to the memory region 2 (neuron parameters):
        spec.switch_write_focus(
            region=constants.POPULATION_BASED_REGIONS.NEURON_PARAMS.value)

        # Write the random back off value
        max_offset = (machine_time_step *
                      time_scale_factor) // _MAX_OFFSET_DENOMINATOR
        spec.write_value(
            int(math.ceil(max_offset / self.__n_subvertices)) *
            self.__n_data_specs)
        self.__n_data_specs += 1

        # Write the number of microseconds between sending spikes
        time_between_spikes = ((machine_time_step * time_scale_factor) /
                               (n_atoms * 2.0))
        spec.write_value(data=int(time_between_spikes))

        # Write whether the key is to be used, and then the key, or 0 if it
        # isn't to be used
        if key is None:
            spec.write_value(data=0)
            spec.write_value(data=0)
        else:
            spec.write_value(data=1)
            spec.write_value(data=key)

        # Write the number of neurons in the block:
        spec.write_value(data=n_atoms)

        # Write the number of synapse types
        spec.write_value(data=self.__neuron_impl.get_n_synapse_types())

        # Write the size of the incoming spike buffer
        spec.write_value(data=self.__incoming_spike_buffer_size)

        # Write the number of variables that can be recorded
        spec.write_value(
            data=len(self.__neuron_impl.get_recordable_variables()))

        # Write the recording data
        recording_data = self.__neuron_recorder.get_data(vertex_slice)
        spec.write_array(recording_data)

        # Write the neuron parameters
        neuron_data = self.__neuron_impl.get_data(self._parameters,
                                                  self._state_variables,
                                                  vertex_slice)
        spec.write_array(neuron_data)

    @inject_items({
        "machine_time_step": "MachineTimeStep",
        "time_scale_factor": "TimeScaleFactor",
        "graph_mapper": "MemoryGraphMapper",
        "routing_info": "MemoryRoutingInfos"
    })
    @overrides(AbstractRewritesDataSpecification.regenerate_data_specification,
               additional_arguments={
                   "machine_time_step", "time_scale_factor", "graph_mapper",
                   "routing_info"
               })
    def regenerate_data_specification(self, spec, placement, machine_time_step,
                                      time_scale_factor, graph_mapper,
                                      routing_info):
        # pylint: disable=too-many-arguments, arguments-differ
        vertex_slice = graph_mapper.get_slice(placement.vertex)

        # reserve the neuron parameters data region
        self._reserve_neuron_params_data_region(
            spec, graph_mapper.get_slice(placement.vertex))

        # write the neuron params into the new DSG region
        self._write_neuron_parameters(
            key=routing_info.get_first_key_from_pre_vertex(
                placement.vertex, constants.SPIKE_PARTITION_ID),
            machine_time_step=machine_time_step,
            spec=spec,
            time_scale_factor=time_scale_factor,
            vertex_slice=vertex_slice)

        # close spec
        spec.end_specification()

    @overrides(AbstractRewritesDataSpecification.
               requires_memory_regions_to_be_reloaded)
    def requires_memory_regions_to_be_reloaded(self):
        return self.__change_requires_neuron_parameters_reload

    @overrides(AbstractRewritesDataSpecification.mark_regions_reloaded)
    def mark_regions_reloaded(self):
        self.__change_requires_neuron_parameters_reload = False

    @inject_items({
        "machine_time_step": "MachineTimeStep",
        "time_scale_factor": "TimeScaleFactor",
        "graph_mapper": "MemoryGraphMapper",
        "application_graph": "MemoryApplicationGraph",
        "machine_graph": "MemoryMachineGraph",
        "routing_info": "MemoryRoutingInfos",
        "data_n_time_steps": "DataNTimeSteps"
    })
    @overrides(AbstractGeneratesDataSpecification.generate_data_specification,
               additional_arguments={
                   "machine_time_step", "time_scale_factor", "graph_mapper",
                   "application_graph", "machine_graph", "routing_info",
                   "data_n_time_steps"
               })
    def generate_data_specification(self, spec, placement, machine_time_step,
                                    time_scale_factor, graph_mapper,
                                    application_graph, machine_graph,
                                    routing_info, data_n_time_steps):
        # pylint: disable=too-many-arguments, arguments-differ
        vertex = placement.vertex

        spec.comment("\n*** Spec for block of {} neurons ***\n".format(
            self.__neuron_impl.model_name))
        vertex_slice = graph_mapper.get_slice(vertex)

        # Reserve memory regions
        self._reserve_memory_regions(spec, vertex_slice, vertex)

        # Declare random number generators and distributions:
        # TODO add random distribution stuff
        # self.write_random_distribution_declarations(spec)

        # Get the key
        key = routing_info.get_first_key_from_pre_vertex(
            vertex, constants.SPIKE_PARTITION_ID)

        # Write the setup region
        spec.switch_write_focus(
            constants.POPULATION_BASED_REGIONS.SYSTEM.value)
        spec.write_array(
            simulation_utilities.get_simulation_header_array(
                self.get_binary_file_name(), machine_time_step,
                time_scale_factor))

        # Write the recording region
        spec.switch_write_focus(
            constants.POPULATION_BASED_REGIONS.RECORDING.value)
        spec.write_array(
            recording_utilities.get_recording_header_array(
                self._get_buffered_sdram(vertex_slice, data_n_time_steps)))

        # Write the neuron parameters
        self._write_neuron_parameters(spec, key, vertex_slice,
                                      machine_time_step, time_scale_factor)

        # write profile data
        profile_utils.write_profile_region_data(
            spec, constants.POPULATION_BASED_REGIONS.PROFILING.value,
            self.__n_profile_samples)

        # Get the weight_scale value from the appropriate location
        weight_scale = self.__neuron_impl.get_global_weight_scale()

        # allow the synaptic matrix to write its data spec-able data
        self.__synapse_manager.write_data_spec(spec, self, vertex_slice,
                                               vertex, placement,
                                               machine_graph,
                                               application_graph, routing_info,
                                               graph_mapper, weight_scale,
                                               machine_time_step)

        # End the writing of this specification:
        spec.end_specification()

    @overrides(AbstractHasAssociatedBinary.get_binary_file_name)
    def get_binary_file_name(self):

        # Split binary name into title and extension
        binary_title, binary_extension = os.path.splitext(
            self.__neuron_impl.binary_name)

        # Reunite title and extension and return
        return (binary_title +
                self.__synapse_manager.vertex_executable_suffix +
                binary_extension)

    @overrides(AbstractHasAssociatedBinary.get_binary_start_type)
    def get_binary_start_type(self):
        return ExecutableType.USES_SIMULATION_INTERFACE

    @overrides(AbstractSpikeRecordable.is_recording_spikes)
    def is_recording_spikes(self):
        return self.__neuron_recorder.is_recording("spikes")

    @overrides(AbstractSpikeRecordable.set_recording_spikes)
    def set_recording_spikes(self,
                             new_state=True,
                             sampling_interval=None,
                             indexes=None):
        self.set_recording("spikes", new_state, sampling_interval, indexes)

    @overrides(AbstractSpikeRecordable.get_spikes)
    def get_spikes(self, placements, graph_mapper, buffer_manager,
                   machine_time_step):
        return self.__neuron_recorder.get_spikes(self.label, buffer_manager,
                                                 self.SPIKE_RECORDING_REGION,
                                                 placements, graph_mapper,
                                                 self, machine_time_step)

    @overrides(AbstractNeuronRecordable.get_recordable_variables)
    def get_recordable_variables(self):
        return self.__neuron_recorder.get_recordable_variables()

    @overrides(AbstractNeuronRecordable.is_recording)
    def is_recording(self, variable):
        return self.__neuron_recorder.is_recording(variable)

    @overrides(AbstractNeuronRecordable.set_recording)
    def set_recording(self,
                      variable,
                      new_state=True,
                      sampling_interval=None,
                      indexes=None):
        self.__change_requires_mapping = not self.is_recording(variable)
        self.__neuron_recorder.set_recording(variable, new_state,
                                             sampling_interval, indexes)

    @overrides(AbstractNeuronRecordable.get_data)
    def get_data(self, variable, n_machine_time_steps, placements,
                 graph_mapper, buffer_manager, machine_time_step):
        # pylint: disable=too-many-arguments
        index = 0
        if variable != "spikes":
            index = 1 + self.__neuron_impl.get_recordable_variable_index(
                variable)
        return self.__neuron_recorder.get_matrix_data(self.label,
                                                      buffer_manager, index,
                                                      placements, graph_mapper,
                                                      self, variable,
                                                      n_machine_time_steps)

    @overrides(AbstractNeuronRecordable.get_neuron_sampling_interval)
    def get_neuron_sampling_interval(self, variable):
        return self.__neuron_recorder.get_neuron_sampling_interval(variable)

    @overrides(AbstractSpikeRecordable.get_spikes_sampling_interval)
    def get_spikes_sampling_interval(self):
        return self.__neuron_recorder.get_neuron_sampling_interval("spikes")

    @overrides(AbstractPopulationInitializable.initialize)
    def initialize(self, variable, value):
        if not self.__has_reset_last:
            raise Exception(
                "initialize can only be called before the first call to run, "
                "or before the first call to run after a reset")
        if variable not in self._state_variables:
            raise KeyError("Vertex does not support initialisation of"
                           " parameter {}".format(variable))
        self._state_variables.set_value(variable, value)
        self.__updated_state_variables.add(variable)
        self.__change_requires_neuron_parameters_reload = True

    @property
    def initialize_parameters(self):
        return self.__pynn_model.default_initial_values.keys()

    def _get_parameter(self, variable):
        if variable.endswith("_init"):
            # method called with "V_init"
            key = variable[:-5]
            if variable in self._state_variables:
                # variable is v and parameter is v_init
                return variable
            elif key in self._state_variables:
                # Oops neuron defines v and not v_init
                return key
        else:
            # method called with "v"
            if variable + "_init" in self._state_variables:
                # variable is v and parameter is v_init
                return variable + "_init"
            if variable in self._state_variables:
                # Oops neuron defines v and not v_init
                return variable

        # parameter not found for this variable
        raise KeyError("No variable {} found in {}".format(
            variable, self.__neuron_impl.model_name))

    @overrides(AbstractPopulationInitializable.get_initial_value)
    def get_initial_value(self, variable, selector=None):
        parameter = self._get_parameter(variable)

        ranged_list = self._state_variables[parameter]
        if selector is None:
            return ranged_list
        return ranged_list.get_values(selector)

    @overrides(AbstractPopulationInitializable.set_initial_value)
    def set_initial_value(self, variable, value, selector=None):
        parameter = self._get_parameter(variable)

        ranged_list = self._state_variables[parameter]
        ranged_list.set_value_by_selector(selector, value)

    @property
    def conductance_based(self):
        return self.__neuron_impl.is_conductance_based

    @overrides(AbstractPopulationSettable.get_value)
    def get_value(self, key):
        """ Get a property of the overall model.
        """
        if key not in self._parameters:
            raise InvalidParameterType(
                "Population {} does not have parameter {}".format(
                    self.__neuron_impl.model_name, key))
        return self._parameters[key]

    @overrides(AbstractPopulationSettable.set_value)
    def set_value(self, key, value):
        """ Set a property of the overall model.
        """
        if key not in self._parameters:
            raise InvalidParameterType(
                "Population {} does not have parameter {}".format(
                    self.__neuron_impl.model_name, key))
        self._parameters.set_value(key, value)
        self.__change_requires_neuron_parameters_reload = True

    @overrides(AbstractReadParametersBeforeSet.read_parameters_from_machine)
    def read_parameters_from_machine(self, transceiver, placement,
                                     vertex_slice):

        # locate SDRAM address to where the neuron parameters are stored
        neuron_region_sdram_address = \
            helpful_functions.locate_memory_region_for_placement(
                placement,
                constants.POPULATION_BASED_REGIONS.NEURON_PARAMS.value,
                transceiver)

        # shift past the extra stuff before neuron parameters that we don't
        # need to read
        neuron_parameters_sdram_address = (
            neuron_region_sdram_address +
            self.BYTES_TILL_START_OF_GLOBAL_PARAMETERS)

        # get size of neuron params
        size_of_region = self._get_sdram_usage_for_neuron_params(vertex_slice)
        size_of_region -= self.BYTES_TILL_START_OF_GLOBAL_PARAMETERS

        # get data from the machine
        byte_array = transceiver.read_memory(placement.x, placement.y,
                                             neuron_parameters_sdram_address,
                                             size_of_region)

        # Skip the recorder globals as these are not change on machine
        # Just written out in case data is changed and written back
        offset = self.__neuron_recorder.get_sdram_usage_in_bytes(vertex_slice)

        # update python neuron parameters with the data
        self.__neuron_impl.read_data(byte_array, offset, vertex_slice,
                                     self._parameters, self._state_variables)

    @property
    def weight_scale(self):
        return self.__neuron_impl.get_global_weight_scale()

    @property
    def ring_buffer_sigma(self):
        return self.__synapse_manager.ring_buffer_sigma

    @ring_buffer_sigma.setter
    def ring_buffer_sigma(self, ring_buffer_sigma):
        self.__synapse_manager.ring_buffer_sigma = ring_buffer_sigma

    @property
    def spikes_per_second(self):
        return self.__synapse_manager.spikes_per_second

    @spikes_per_second.setter
    def spikes_per_second(self, spikes_per_second):
        self.__synapse_manager.spikes_per_second = spikes_per_second

    @property
    def synapse_dynamics(self):
        return self.__synapse_manager.synapse_dynamics

    def set_synapse_dynamics(self, synapse_dynamics):
        self.__synapse_manager.synapse_dynamics = synapse_dynamics

    def add_pre_run_connection_holder(self, connection_holder, edge,
                                      synapse_info):
        # pylint: disable=arguments-differ
        self.__synapse_manager.add_pre_run_connection_holder(
            connection_holder, edge, synapse_info)

    def get_connection_holders(self):
        return self.__synapse_manager.get_connection_holders()

    @overrides(AbstractAcceptsIncomingSynapses.get_connections_from_machine)
    def get_connections_from_machine(self,
                                     transceiver,
                                     placement,
                                     edge,
                                     graph_mapper,
                                     routing_infos,
                                     synapse_information,
                                     machine_time_step,
                                     using_extra_monitor_cores,
                                     placements=None,
                                     monitor_api=None,
                                     monitor_placement=None,
                                     monitor_cores=None,
                                     handle_time_out_configuration=True,
                                     fixed_routes=None):
        # pylint: disable=too-many-arguments
        return self.__synapse_manager.get_connections_from_machine(
            transceiver, placement, edge, graph_mapper, routing_infos,
            synapse_information, machine_time_step, using_extra_monitor_cores,
            placements, monitor_api, monitor_placement, monitor_cores,
            handle_time_out_configuration, fixed_routes)

    def clear_connection_cache(self):
        self.__synapse_manager.clear_connection_cache()

    def get_maximum_delay_supported_in_ms(self, machine_time_step):
        return self.__synapse_manager.get_maximum_delay_supported_in_ms(
            machine_time_step)

    @overrides(AbstractProvidesIncomingPartitionConstraints.
               get_incoming_partition_constraints)
    def get_incoming_partition_constraints(self, partition):
        """ Gets the constraints for partitions going into this vertex.

        :param partition: partition that goes into this vertex
        :return: list of constraints
        """
        return self.__synapse_manager.get_incoming_partition_constraints()

    @overrides(AbstractProvidesOutgoingPartitionConstraints.
               get_outgoing_partition_constraints)
    def get_outgoing_partition_constraints(self, partition):
        """ Gets the constraints for partitions going out of this vertex.

        :param partition: the partition that leaves this vertex
        :return: list of constraints
        """
        return [ContiguousKeyRangeContraint()]

    @overrides(AbstractNeuronRecordable.clear_recording)
    def clear_recording(self, variable, buffer_manager, placements,
                        graph_mapper):
        index = 0
        if variable != "spikes":
            index = 1 + self.__neuron_impl.get_recordable_variable_index(
                variable)
        self._clear_recording_region(buffer_manager, placements, graph_mapper,
                                     index)

    @overrides(AbstractSpikeRecordable.clear_spike_recording)
    def clear_spike_recording(self, buffer_manager, placements, graph_mapper):
        self._clear_recording_region(
            buffer_manager, placements, graph_mapper,
            AbstractPopulationVertex.SPIKE_RECORDING_REGION)

    def _clear_recording_region(self, buffer_manager, placements, graph_mapper,
                                recording_region_id):
        """ Clear a recorded data region from the buffer manager.

        :param buffer_manager: the buffer manager object
        :param placements: the placements object
        :param graph_mapper: the graph mapper object
        :param recording_region_id: the recorded region ID for clearing
        :rtype: None
        """
        machine_vertices = graph_mapper.get_machine_vertices(self)
        for machine_vertex in machine_vertices:
            placement = placements.get_placement_of_vertex(machine_vertex)
            buffer_manager.clear_recorded_data(placement.x, placement.y,
                                               placement.p,
                                               recording_region_id)

    @overrides(AbstractContainsUnits.get_units)
    def get_units(self, variable):
        if self.__neuron_impl.is_recordable(variable):
            return self.__neuron_impl.get_recordable_units(variable)
        if variable not in self._parameters:
            raise Exception("Population {} does not have parameter {}".format(
                self.__neuron_impl.model_name, variable))
        return self.__neuron_impl.get_units(variable)

    def describe(self):
        """ Get a human-readable description of the cell or synapse type.

        The output may be customised by specifying a different template\
        together with an associated template engine\
        (see ``pyNN.descriptions``).

        If template is None, then a dictionary containing the template context\
        will be returned.
        """
        parameters = dict()
        for parameter_name in self.__pynn_model.default_parameters:
            parameters[parameter_name] = self.get_value(parameter_name)

        context = {
            "name": self.__neuron_impl.model_name,
            "default_parameters": self.__pynn_model.default_parameters,
            "default_initial_values": self.__pynn_model.default_parameters,
            "parameters": parameters,
        }
        return context

    def get_synapse_id_by_target(self, target):
        return self.__neuron_impl.get_synapse_id_by_target(target)

    def __str__(self):
        return "{} with {} atoms".format(self.label, self.n_atoms)

    def __repr__(self):
        return self.__str__()

    def gen_on_machine(self, vertex_slice):
        return self.__synapse_manager.gen_on_machine(vertex_slice)

    @overrides(AbstractCanReset.reset_to_first_timestep)
    def reset_to_first_timestep(self):
        # Mark that reset has been done, and reload state variables
        self.__has_reset_last = True
        self.__change_requires_neuron_parameters_reload = True

        # If synapses change during the run,
        if self.__synapse_manager.synapse_dynamics.changes_during_run:
            self.__change_requires_data_generation = True
            self.__change_requires_neuron_parameters_reload = False
class AbstractPopulationVertex(
        ApplicationVertex, AbstractGeneratesDataSpecification,
        AbstractHasAssociatedBinary, AbstractContainsUnits,
        AbstractSpikeRecordable,  AbstractNeuronRecordable,
        AbstractProvidesOutgoingPartitionConstraints,
        AbstractProvidesIncomingPartitionConstraints,
        AbstractPopulationInitializable, AbstractPopulationSettable,
        AbstractChangableAfterRun,
        AbstractRewritesDataSpecification, AbstractReadParametersBeforeSet,
        AbstractAcceptsIncomingSynapses, ProvidesKeyToAtomMappingImpl):
    """ Underlying vertex model for Neural Populations.
    """
    __slots__ = [
        "_change_requires_mapping",
        "_change_requires_neuron_parameters_reload",
        "_incoming_spike_buffer_size",
        "_n_atoms",
        "_n_profile_samples",
        "_neuron_impl",
        "_neuron_recorder",
        "_parameters",
        "_pynn_model",
        "_state_variables",
        "_synapse_manager",
        "_time_between_requests",
        "_units",
        "_n_subvertices",
        "_n_data_specs"]

    BASIC_MALLOC_USAGE = 2

    # recording region IDs
    SPIKE_RECORDING_REGION = 0

    # the size of the runtime SDP port data region
    RUNTIME_SDP_PORT_SIZE = 4

    # 8 elements before the start of global parameters
    BYTES_TILL_START_OF_GLOBAL_PARAMETERS = 32

    # The Buffer traffic type
    TRAFFIC_IDENTIFIER = "BufferTraffic"

    _n_vertices = 0

    def __init__(
            self, n_neurons, label, constraints, max_atoms_per_core,
            spikes_per_second, ring_buffer_sigma, incoming_spike_buffer_size,
            neuron_impl, pynn_model):
        # pylint: disable=too-many-arguments, too-many-locals
        super(AbstractPopulationVertex, self).__init__(
            label, constraints, max_atoms_per_core)

        self._n_atoms = n_neurons
        self._n_subvertices = 0
        self._n_data_specs = 0

        # buffer data
        self._incoming_spike_buffer_size = incoming_spike_buffer_size

        # get config from simulator
        config = globals_variables.get_simulator().config

        if incoming_spike_buffer_size is None:
            self._incoming_spike_buffer_size = config.getint(
                "Simulation", "incoming_spike_buffer_size")

        self._neuron_impl = neuron_impl
        self._pynn_model = pynn_model
        self._parameters = SpynnakerRangeDictionary(n_neurons)
        self._state_variables = SpynnakerRangeDictionary(n_neurons)
        self._neuron_impl.add_parameters(self._parameters)
        self._neuron_impl.add_state_variables(self._state_variables)

        # Set up for recording
        recordables = ["spikes"]
        recordables.extend(self._neuron_impl.get_recordable_variables())
        self._neuron_recorder = NeuronRecorder(recordables, n_neurons)

        # Set up synapse handling
        self._synapse_manager = SynapticManager(
            self._neuron_impl.get_n_synapse_types(), ring_buffer_sigma,
            spikes_per_second, config)

        # bool for if state has changed.
        self._change_requires_mapping = True
        self._change_requires_neuron_parameters_reload = False

        # Set up for profiling
        self._n_profile_samples = helpful_functions.read_config_int(
            config, "Reports", "n_profile_samples")

    @property
    @overrides(ApplicationVertex.n_atoms)
    def n_atoms(self):
        return self._n_atoms

    @inject_items({
        "graph": "MemoryApplicationGraph",
        "machine_time_step": "MachineTimeStep"
    })
    @overrides(
        ApplicationVertex.get_resources_used_by_atoms,
        additional_arguments={
            "graph", "machine_time_step"
        }
    )
    def get_resources_used_by_atoms(
            self, vertex_slice, graph, machine_time_step):
        # pylint: disable=arguments-differ

        variableSDRAM = self._neuron_recorder.get_variable_sdram_usage(
            vertex_slice)
        constantSDRAM = ConstantSDRAM(
                self._get_sdram_usage_for_atoms(
                    vertex_slice, graph, machine_time_step))

        # set resources required from this object
        container = ResourceContainer(
            sdram=variableSDRAM + constantSDRAM,
            dtcm=DTCMResource(self.get_dtcm_usage_for_atoms(vertex_slice)),
            cpu_cycles=CPUCyclesPerTickResource(
                self.get_cpu_usage_for_atoms(vertex_slice)))

        # return the total resources.
        return container

    @property
    @overrides(AbstractChangableAfterRun.requires_mapping)
    def requires_mapping(self):
        return self._change_requires_mapping

    @overrides(AbstractChangableAfterRun.mark_no_changes)
    def mark_no_changes(self):
        self._change_requires_mapping = False

    # CB: May be dead code
    def _get_buffered_sdram_per_timestep(self, vertex_slice):
        values = [self._neuron_recorder.get_buffered_sdram_per_timestep(
                "spikes", vertex_slice)]
        for variable in self._neuron_impl.get_recordable_variables():
            values.append(
                self._neuron_recorder.get_buffered_sdram_per_timestep(
                    variable, vertex_slice))
        return values

    def _get_buffered_sdram(self, vertex_slice, n_machine_time_steps):
        values = [self._neuron_recorder.get_buffered_sdram(
                "spikes", vertex_slice, n_machine_time_steps)]
        for variable in self._neuron_impl.get_recordable_variables():
            values.append(
                self._neuron_recorder.get_buffered_sdram(
                    variable, vertex_slice, n_machine_time_steps))
        return values

    @overrides(ApplicationVertex.create_machine_vertex)
    def create_machine_vertex(
            self, vertex_slice, resources_required, label=None,
            constraints=None):

        self._n_subvertices += 1
        return PopulationMachineVertex(
            resources_required, self._neuron_recorder.recorded_region_ids,
            label, constraints)

    def get_cpu_usage_for_atoms(self, vertex_slice):
        return (
            _NEURON_BASE_N_CPU_CYCLES + _C_MAIN_BASE_N_CPU_CYCLES +
            (_NEURON_BASE_N_CPU_CYCLES_PER_NEURON * vertex_slice.n_atoms) +
            self._neuron_recorder.get_n_cpu_cycles(vertex_slice.n_atoms) +
            self._neuron_impl.get_n_cpu_cycles(vertex_slice.n_atoms) +
            self._synapse_manager.get_n_cpu_cycles())

    def get_dtcm_usage_for_atoms(self, vertex_slice):
        return (
            _NEURON_BASE_DTCM_USAGE_IN_BYTES +
            self._neuron_impl.get_dtcm_usage_in_bytes(vertex_slice.n_atoms) +
            self._neuron_recorder.get_dtcm_usage_in_bytes(vertex_slice) +
            self._synapse_manager.get_dtcm_usage_in_bytes())

    def _get_sdram_usage_for_neuron_params(self, vertex_slice):
        """ Calculate the SDRAM usage for just the neuron parameters region.

        :param vertex_slice: the slice of atoms.
        :return: The SDRAM required for the neuron region
        """
        return (
            self.BYTES_TILL_START_OF_GLOBAL_PARAMETERS +
            self._neuron_recorder.get_sdram_usage_in_bytes(vertex_slice) +
            self._neuron_impl.get_sdram_usage_in_bytes(vertex_slice.n_atoms))

    def _get_sdram_usage_for_atoms(
            self, vertex_slice, graph, machine_time_step):
        n_record = len(self._neuron_impl.get_recordable_variables()) + 1
        sdram_requirement = (
            common_constants.SYSTEM_BYTES_REQUIREMENT +
            self._get_sdram_usage_for_neuron_params(vertex_slice) +
            recording_utilities.get_recording_header_size(n_record) +
            recording_utilities.get_recording_data_constant_size(n_record) +
            PopulationMachineVertex.get_provenance_data_size(
                PopulationMachineVertex.N_ADDITIONAL_PROVENANCE_DATA_ITEMS) +
            self._synapse_manager.get_sdram_usage_in_bytes(
                vertex_slice, graph.get_edges_ending_at_vertex(self),
                machine_time_step) +
            profile_utils.get_profile_region_size(
                self._n_profile_samples))

        return sdram_requirement

    def _reserve_memory_regions(self, spec, vertex_slice, vertex):

        spec.comment("\nReserving memory space for data regions:\n\n")

        # Reserve memory:
        spec.reserve_memory_region(
            region=constants.POPULATION_BASED_REGIONS.SYSTEM.value,
            size=common_constants.SIMULATION_N_BYTES,
            label='System')

        self._reserve_neuron_params_data_region(spec, vertex_slice)

        spec.reserve_memory_region(
            region=constants.POPULATION_BASED_REGIONS.RECORDING.value,
            size=recording_utilities.get_recording_header_size(
                len(self._neuron_impl.get_recordable_variables()) + 1))

        profile_utils.reserve_profile_region(
            spec, constants.POPULATION_BASED_REGIONS.PROFILING.value,
            self._n_profile_samples)

        vertex.reserve_provenance_data_region(spec)

    def _reserve_neuron_params_data_region(self, spec, vertex_slice):
        """ Reserve the neuron parameter data region.

        :param spec: the spec to write the DSG region to
        :param vertex_slice: the slice of atoms from the application vertex
        :return: None
        """
        params_size = self._get_sdram_usage_for_neuron_params(vertex_slice)
        spec.reserve_memory_region(
            region=constants.POPULATION_BASED_REGIONS.NEURON_PARAMS.value,
            size=params_size,
            label='NeuronParams')

    def _write_neuron_parameters(
            self, spec, key, vertex_slice, machine_time_step,
            time_scale_factor):
        # pylint: disable=too-many-arguments
        n_atoms = vertex_slice.n_atoms
        spec.comment("\nWriting Neuron Parameters for {} Neurons:\n".format(
            n_atoms))

        # Set the focus to the memory region 2 (neuron parameters):
        spec.switch_write_focus(
            region=constants.POPULATION_BASED_REGIONS.NEURON_PARAMS.value)

        # Write the random back off value
        max_offset = (
            machine_time_step * time_scale_factor) // _MAX_OFFSET_DENOMINATOR
        spec.write_value(
            int(math.ceil(max_offset / self._n_subvertices)) *
            self._n_data_specs)
        self._n_data_specs += 1

        # Write the number of microseconds between sending spikes
        time_between_spikes = (
            (machine_time_step * time_scale_factor) / (n_atoms * 2.0))
        spec.write_value(data=int(time_between_spikes))

        # Write whether the key is to be used, and then the key, or 0 if it
        # isn't to be used
        if key is None:
            spec.write_value(data=0)
            spec.write_value(data=0)
        else:
            spec.write_value(data=1)
            spec.write_value(data=key)

        # Write the number of neurons in the block:
        spec.write_value(data=n_atoms)

        # Write the number of synapse types
        spec.write_value(data=self._neuron_impl.get_n_synapse_types())

        # Write the size of the incoming spike buffer
        spec.write_value(data=self._incoming_spike_buffer_size)

        # Write the number of variables that can be recorded
        spec.write_value(
            data=len(self._neuron_impl.get_recordable_variables()))

        # Write the recording data
        recording_data = self._neuron_recorder.get_data(vertex_slice)
        spec.write_array(recording_data)

        # Write the neuron parameters
        neuron_data = self._neuron_impl.get_data(
            self._parameters, self._state_variables, vertex_slice)
        spec.write_array(neuron_data)

    @inject_items({
        "machine_time_step": "MachineTimeStep",
        "time_scale_factor": "TimeScaleFactor",
        "graph_mapper": "MemoryGraphMapper",
        "routing_info": "MemoryRoutingInfos"})
    @overrides(
        AbstractRewritesDataSpecification.regenerate_data_specification,
        additional_arguments={
            "machine_time_step", "time_scale_factor", "graph_mapper",
            "routing_info"})
    def regenerate_data_specification(
            self, spec, placement, machine_time_step, time_scale_factor,
            graph_mapper, routing_info):
        # pylint: disable=too-many-arguments, arguments-differ
        vertex_slice = graph_mapper.get_slice(placement.vertex)

        # reserve the neuron parameters data region
        self._reserve_neuron_params_data_region(
            spec, graph_mapper.get_slice(placement.vertex))

        # write the neuron params into the new DSG region
        self._write_neuron_parameters(
            key=routing_info.get_first_key_from_pre_vertex(
                placement.vertex, constants.SPIKE_PARTITION_ID),
            machine_time_step=machine_time_step, spec=spec,
            time_scale_factor=time_scale_factor,
            vertex_slice=vertex_slice)

        # close spec
        spec.end_specification()

    @overrides(AbstractRewritesDataSpecification
               .requires_memory_regions_to_be_reloaded)
    def requires_memory_regions_to_be_reloaded(self):
        return self._change_requires_neuron_parameters_reload

    @overrides(AbstractRewritesDataSpecification.mark_regions_reloaded)
    def mark_regions_reloaded(self):
        self._change_requires_neuron_parameters_reload = False

    @inject_items({
        "machine_time_step": "MachineTimeStep",
        "time_scale_factor": "TimeScaleFactor",
        "graph_mapper": "MemoryGraphMapper",
        "application_graph": "MemoryApplicationGraph",
        "machine_graph": "MemoryMachineGraph",
        "routing_info": "MemoryRoutingInfos",
        "data_n_time_steps": "DataNTimeSteps",
        "placements": "MemoryPlacements"
    })
    @overrides(
        AbstractGeneratesDataSpecification.generate_data_specification,
        additional_arguments={
            "machine_time_step", "time_scale_factor", "graph_mapper",
            "application_graph", "machine_graph", "routing_info",
            "data_n_time_steps", "placements"
        })
    def generate_data_specification(
            self, spec, placement, machine_time_step, time_scale_factor,
            graph_mapper, application_graph, machine_graph, routing_info,
            data_n_time_steps, placements):
        # pylint: disable=too-many-arguments, arguments-differ
        vertex = placement.vertex

        spec.comment("\n*** Spec for block of {} neurons ***\n".format(
            self._neuron_impl.model_name))
        vertex_slice = graph_mapper.get_slice(vertex)

        # Reserve memory regions
        self._reserve_memory_regions(spec, vertex_slice, vertex)

        # Declare random number generators and distributions:
        # TODO add random distribution stuff
        # self.write_random_distribution_declarations(spec)

        # Get the key
        key = routing_info.get_first_key_from_pre_vertex(
            vertex, constants.SPIKE_PARTITION_ID)

        # Write the setup region
        spec.switch_write_focus(
            constants.POPULATION_BASED_REGIONS.SYSTEM.value)
        spec.write_array(simulation_utilities.get_simulation_header_array(
            self.get_binary_file_name(), machine_time_step,
            time_scale_factor))

        # Write the recording region
        spec.switch_write_focus(
            constants.POPULATION_BASED_REGIONS.RECORDING.value)
        spec.write_array(recording_utilities.get_recording_header_array(
            self._get_buffered_sdram(vertex_slice, data_n_time_steps)))

        # Write the neuron parameters
        self._write_neuron_parameters(
            spec, key, vertex_slice, machine_time_step, time_scale_factor)

        # write profile data
        profile_utils.write_profile_region_data(
            spec, constants.POPULATION_BASED_REGIONS.PROFILING.value,
            self._n_profile_samples)

        # Get the weight_scale value from the appropriate location
        weight_scale = self._neuron_impl.get_global_weight_scale()

        # allow the synaptic matrix to write its data spec-able data
        self._synapse_manager.write_data_spec(
            spec, self, vertex_slice, vertex, placement, machine_graph,
            application_graph, routing_info, graph_mapper,
            weight_scale, machine_time_step, placements)

        # End the writing of this specification:
        spec.end_specification()

    @overrides(AbstractHasAssociatedBinary.get_binary_file_name)
    def get_binary_file_name(self):

        # Split binary name into title and extension
        binary_title, binary_extension = os.path.splitext(
            self._neuron_impl.binary_name)

        # Reunite title and extension and return
        return (binary_title + self._synapse_manager.vertex_executable_suffix +
                binary_extension)

    @overrides(AbstractHasAssociatedBinary.get_binary_start_type)
    def get_binary_start_type(self):
        return ExecutableType.USES_SIMULATION_INTERFACE

    @overrides(AbstractSpikeRecordable.is_recording_spikes)
    def is_recording_spikes(self):
        return self._neuron_recorder.is_recording("spikes")

    @overrides(AbstractSpikeRecordable.set_recording_spikes)
    def set_recording_spikes(
            self, new_state=True, sampling_interval=None, indexes=None):
        self.set_recording("spikes", new_state, sampling_interval, indexes)

    @overrides(AbstractSpikeRecordable.get_spikes)
    def get_spikes(
            self, placements, graph_mapper, buffer_manager, machine_time_step):
        return self._neuron_recorder.get_spikes(
            self.label, buffer_manager, self.SPIKE_RECORDING_REGION,
            placements, graph_mapper, self, machine_time_step)

    @overrides(AbstractNeuronRecordable.get_recordable_variables)
    def get_recordable_variables(self):
        return self._neuron_recorder.get_recordable_variables()

    @overrides(AbstractNeuronRecordable.is_recording)
    def is_recording(self, variable):
        return self._neuron_recorder.is_recording(variable)

    @overrides(AbstractNeuronRecordable.set_recording)
    def set_recording(self, variable, new_state=True, sampling_interval=None,
                      indexes=None):
        self._change_requires_mapping = not self.is_recording(variable)
        self._neuron_recorder.set_recording(
            variable, new_state, sampling_interval, indexes)

    @overrides(AbstractNeuronRecordable.get_data)
    def get_data(self, variable, n_machine_time_steps, placements,
                 graph_mapper, buffer_manager, machine_time_step):
        # pylint: disable=too-many-arguments
        index = 0
        if variable != "spikes":
            index = 1 + self._neuron_impl.get_recordable_variable_index(
                variable)
        return self._neuron_recorder.get_matrix_data(
            self.label, buffer_manager, index, placements, graph_mapper,
            self, variable, n_machine_time_steps)

    @overrides(AbstractNeuronRecordable.get_neuron_sampling_interval)
    def get_neuron_sampling_interval(self, variable):
        return self._neuron_recorder.get_neuron_sampling_interval(variable)

    @overrides(AbstractSpikeRecordable.get_spikes_sampling_interval)
    def get_spikes_sampling_interval(self):
        return self._neuron_recorder.get_neuron_sampling_interval("spikes")

    @overrides(AbstractPopulationInitializable.initialize)
    def initialize(self, variable, value):
        if variable not in self._state_variables:
            raise KeyError(
                "Vertex does not support initialisation of"
                " parameter {}".format(variable))
        self._state_variables.set_value(variable, value)
        self._change_requires_neuron_parameters_reload = True

    @property
    def initialize_parameters(self):
        return self._pynn_model.default_initial_values.keys()

    def _get_parameter(self, variable):
        if variable.endswith("_init"):
            # method called with "V_init"
            key = variable[:-5]
            if variable in self._state_variables:
                # variable is v and parameter is v_init
                return variable
            elif key in self._state_variables:
                # Oops neuron defines v and not v_init
                return key
        else:
            # method called with "v"
            if variable + "_init" in self._state_variables:
                # variable is v and parameter is v_init
                return variable + "_init"
            if variable in self._state_variables:
                # Oops neuron defines v and not v_init
                return variable

        # parameter not found for this variable
        raise KeyError("No variable {} found in {}".format(
            variable, self._neuron_impl.model_name))

    @overrides(AbstractPopulationInitializable.get_initial_value)
    def get_initial_value(self, variable, selector=None):
        parameter = self._get_parameter(variable)

        ranged_list = self._state_variables[parameter]
        if selector is None:
            return ranged_list
        return ranged_list.get_values(selector)

    @overrides(AbstractPopulationInitializable.set_initial_value)
    def set_initial_value(self, variable, value, selector=None):
        parameter = self._get_parameter(variable)

        ranged_list = self._state_variables[parameter]
        ranged_list.set_value_by_selector(selector, value)

    @property
    def conductance_based(self):
        return self._neuron_impl.is_conductance_based

    @overrides(AbstractPopulationSettable.get_value)
    def get_value(self, key):
        """ Get a property of the overall model.
        """
        if key not in self._parameters:
            raise InvalidParameterType(
                "Population {} does not have parameter {}".format(
                    self._neuron_impl.model_name, key))
        return self._parameters[key]

    @overrides(AbstractPopulationSettable.set_value)
    def set_value(self, key, value):
        """ Set a property of the overall model.
        """
        if key not in self._parameters:
            raise InvalidParameterType(
                "Population {} does not have parameter {}".format(
                    self._neuron_impl.model_name, key))
        self._parameters.set_value(key, value)
        self._change_requires_neuron_parameters_reload = True

    @overrides(AbstractReadParametersBeforeSet.read_parameters_from_machine)
    def read_parameters_from_machine(
            self, transceiver, placement, vertex_slice):

        # locate SDRAM address to where the neuron parameters are stored
        neuron_region_sdram_address = \
            helpful_functions.locate_memory_region_for_placement(
                placement,
                constants.POPULATION_BASED_REGIONS.NEURON_PARAMS.value,
                transceiver)

        # shift past the extra stuff before neuron parameters that we don't
        # need to read
        neuron_parameters_sdram_address = (
            neuron_region_sdram_address +
            self.BYTES_TILL_START_OF_GLOBAL_PARAMETERS)

        # get size of neuron params
        size_of_region = self._get_sdram_usage_for_neuron_params(vertex_slice)
        size_of_region -= self.BYTES_TILL_START_OF_GLOBAL_PARAMETERS

        # get data from the machine
        byte_array = transceiver.read_memory(
            placement.x, placement.y, neuron_parameters_sdram_address,
            size_of_region)

        # Skip the recorder globals as these are not change on machine
        # Just written out in case data is changed and written back
        offset = self._neuron_recorder.get_sdram_usage_in_bytes(
            vertex_slice)

        # update python neuron parameters with the data
        self._neuron_impl.read_data(
            byte_array, offset, vertex_slice, self._parameters,
            self._state_variables)

    @property
    def weight_scale(self):
        return self._neuron_impl.get_global_weight_scale()

    @property
    def ring_buffer_sigma(self):
        return self._synapse_manager.ring_buffer_sigma

    @ring_buffer_sigma.setter
    def ring_buffer_sigma(self, ring_buffer_sigma):
        self._synapse_manager.ring_buffer_sigma = ring_buffer_sigma

    @property
    def spikes_per_second(self):
        return self._synapse_manager.spikes_per_second

    @spikes_per_second.setter
    def spikes_per_second(self, spikes_per_second):
        self._synapse_manager.spikes_per_second = spikes_per_second

    @property
    def synapse_dynamics(self):
        return self._synapse_manager.synapse_dynamics

    def set_synapse_dynamics(self, synapse_dynamics):
        self._synapse_manager.synapse_dynamics = synapse_dynamics

    def add_pre_run_connection_holder(
            self, connection_holder, edge, synapse_info):
        # pylint: disable=arguments-differ
        self._synapse_manager.add_pre_run_connection_holder(
            connection_holder, edge, synapse_info)

    @overrides(AbstractAcceptsIncomingSynapses.get_connections_from_machine)
    def get_connections_from_machine(
            self, transceiver, placement, edge, graph_mapper, routing_infos,
            synapse_information, 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):
        # pylint: disable=too-many-arguments
        return self._synapse_manager.get_connections_from_machine(
            transceiver, placement, edge, graph_mapper,
            routing_infos, synapse_information, machine_time_step,
            using_extra_monitor_cores, placements, data_receiver,
            sender_extra_monitor_core_placement,
            extra_monitor_cores_for_router_timeout,
            handle_time_out_configuration, fixed_routes)

    def clear_connection_cache(self):
        self._synapse_manager.clear_connection_cache()

    def get_maximum_delay_supported_in_ms(self, machine_time_step):
        return self._synapse_manager.get_maximum_delay_supported_in_ms(
            machine_time_step)

    @overrides(AbstractProvidesIncomingPartitionConstraints.
               get_incoming_partition_constraints)
    def get_incoming_partition_constraints(self, partition):
        """ Gets the constraints for partitions going into this vertex.

        :param partition: partition that goes into this vertex
        :return: list of constraints
        """
        return self._synapse_manager.get_incoming_partition_constraints()

    @overrides(AbstractProvidesOutgoingPartitionConstraints.
               get_outgoing_partition_constraints)
    def get_outgoing_partition_constraints(self, partition):
        """ Gets the constraints for partitions going out of this vertex.

        :param partition: the partition that leaves this vertex
        :return: list of constraints
        """
        return [ContiguousKeyRangeContraint()]

    @overrides(
        AbstractNeuronRecordable.clear_recording)
    def clear_recording(
            self, variable, buffer_manager, placements, graph_mapper):
        index = 0
        if variable != "spikes":
            index = 1 + self._neuron_impl.get_recordable_variable_index(
                variable)
        self._clear_recording_region(
            buffer_manager, placements, graph_mapper, index)

    @overrides(AbstractSpikeRecordable.clear_spike_recording)
    def clear_spike_recording(self, buffer_manager, placements, graph_mapper):
        self._clear_recording_region(
            buffer_manager, placements, graph_mapper,
            AbstractPopulationVertex.SPIKE_RECORDING_REGION)

    def _clear_recording_region(
            self, buffer_manager, placements, graph_mapper,
            recording_region_id):
        """ Clear a recorded data region from the buffer manager.

        :param buffer_manager: the buffer manager object
        :param placements: the placements object
        :param graph_mapper: the graph mapper object
        :param recording_region_id: the recorded region ID for clearing
        :rtype: None
        """
        machine_vertices = graph_mapper.get_machine_vertices(self)
        for machine_vertex in machine_vertices:
            placement = placements.get_placement_of_vertex(machine_vertex)
            buffer_manager.clear_recorded_data(
                placement.x, placement.y, placement.p, recording_region_id)

    @overrides(AbstractContainsUnits.get_units)
    def get_units(self, variable):
        if self._neuron_impl.is_recordable(variable):
            return self._neuron_impl.get_recordable_units(variable)
        if variable not in self._parameters:
            raise Exception("Population {} does not have parameter {}".format(
                self._neuron_impl.model_name, variable))
        return self._neuron_impl.get_units(variable)

    def describe(self):
        """ Get a human-readable description of the cell or synapse type.

        The output may be customised by specifying a different template\
        together with an associated template engine\
        (see ``pyNN.descriptions``).

        If template is None, then a dictionary containing the template context\
        will be returned.
        """
        parameters = dict()
        for parameter_name in self._pynn_model.default_parameters:
            parameters[parameter_name] = self.get_value(parameter_name)

        context = {
            "name": self._neuron_impl.model_name,
            "default_parameters": self._pynn_model.default_parameters,
            "default_initial_values": self._pynn_model.default_parameters,
            "parameters": parameters,
        }
        return context

    def get_synapse_id_by_target(self, target):
        return self._neuron_impl.get_synapse_id_by_target(target)

    def __str__(self):
        return "{} with {} atoms".format(self.label, self.n_atoms)

    def __repr__(self):
        return self.__str__()

    def gen_on_machine(self, vertex_slice):
        return self._synapse_manager.gen_on_machine(vertex_slice)
class AbstractPopulationVertex(
        ApplicationVertex, AbstractGeneratesDataSpecification,
        AbstractHasAssociatedBinary, AbstractContainsUnits,
        AbstractSpikeRecordable, AbstractNeuronRecordable,
        AbstractProvidesOutgoingPartitionConstraints,
        AbstractProvidesIncomingPartitionConstraints,
        AbstractPopulationInitializable, AbstractPopulationSettable,
        AbstractChangableAfterRun, AbstractRewritesDataSpecification,
        AbstractReadParametersBeforeSet, AbstractAcceptsIncomingSynapses,
        ProvidesKeyToAtomMappingImpl, AbstractCanReset):
    """ Underlying vertex model for Neural Populations.
        Not actually abstract.
    """

    __slots__ = [
        "__change_requires_mapping",
        "__change_requires_neuron_parameters_reload",
        "__change_requires_data_generation",
        "__incoming_spike_buffer_size",
        "__n_atoms",
        "__n_profile_samples",
        "__neuron_impl",
        "__neuron_recorder",
        "_parameters",  # See AbstractPyNNModel
        "__pynn_model",
        "_state_variables",  # See AbstractPyNNModel
        "__synapse_manager",
        "__time_between_requests",
        "__units",
        "__n_subvertices",
        "__n_data_specs",
        "__initial_state_variables",
        "__has_reset_last",
        "__updated_state_variables"
    ]

    #: recording region IDs
    _SPIKE_RECORDING_REGION = 0

    #: the size of the runtime SDP port data region
    _RUNTIME_SDP_PORT_SIZE = BYTES_PER_WORD

    #: The Buffer traffic type
    _TRAFFIC_IDENTIFIER = "BufferTraffic"

    # 7 elements before the start of global parameters
    # 1. random back off, 2. micro secs before spike, 3. has key, 4. key,
    # 5. n atoms, 6. n synapse types, 7. incoming spike buffer size.
    _BYTES_TILL_START_OF_GLOBAL_PARAMETERS = 7 * BYTES_PER_WORD

    _n_vertices = 0

    def __init__(self, n_neurons, label, constraints, max_atoms_per_core,
                 spikes_per_second, ring_buffer_sigma,
                 incoming_spike_buffer_size, neuron_impl, pynn_model):
        """
        :param int n_neurons: The number of neurons in the population
        :param str label: The label on the population
        :param list(~pacman.model.constraints.AbstractConstraint) constraints:
            Constraints on where a population's vertices may be placed.
        :param int max_atoms_per_core:
            The maximum number of atoms (neurons) per SpiNNaker core.
        :param spikes_per_second: Expected spike rate
        :type spikes_per_second: float or None
        :param ring_buffer_sigma:
            How many SD above the mean to go for upper bound of ring buffer \
            size; a good starting choice is 5.0. Given length of simulation \
            we can set this for approximate number of saturation events.
        :type ring_buffer_sigma: float or None
        :param incoming_spike_buffer_size:
        :type incoming_spike_buffer_size: int or None
        :param AbstractNeuronImpl neuron_impl:
            The (Python side of the) implementation of the neurons themselves.
        :param AbstractPyNNNeuronModel pynn_model:
            The PyNN neuron model that this vertex is working on behalf of.
        """

        # pylint: disable=too-many-arguments, too-many-locals
        ApplicationVertex.__init__(self, label, constraints,
                                   max_atoms_per_core)

        self.__n_atoms = n_neurons
        self.__n_subvertices = 0
        self.__n_data_specs = 0

        # buffer data
        self.__incoming_spike_buffer_size = incoming_spike_buffer_size

        # get config from simulator
        config = globals_variables.get_simulator().config

        if incoming_spike_buffer_size is None:
            self.__incoming_spike_buffer_size = config.getint(
                "Simulation", "incoming_spike_buffer_size")

        self.__neuron_impl = neuron_impl
        self.__pynn_model = pynn_model
        self._parameters = SpynnakerRangeDictionary(n_neurons)
        self._state_variables = SpynnakerRangeDictionary(n_neurons)
        self.__neuron_impl.add_parameters(self._parameters)
        self.__neuron_impl.add_state_variables(self._state_variables)
        self.__initial_state_variables = None
        self.__updated_state_variables = set()

        # Set up for recording
        recordable_variables = list(
            self.__neuron_impl.get_recordable_variables())
        record_data_types = dict(
            self.__neuron_impl.get_recordable_data_types())
        self.__neuron_recorder = NeuronRecorder(recordable_variables,
                                                record_data_types,
                                                [NeuronRecorder.SPIKES],
                                                n_neurons)

        # Set up synapse handling
        self.__synapse_manager = SynapticManager(
            self.__neuron_impl.get_n_synapse_types(), ring_buffer_sigma,
            spikes_per_second, config)

        # bool for if state has changed.
        self.__change_requires_mapping = True
        self.__change_requires_neuron_parameters_reload = False
        self.__change_requires_data_generation = False
        self.__has_reset_last = True

        # Set up for profiling
        self.__n_profile_samples = helpful_functions.read_config_int(
            config, "Reports", "n_profile_samples")

    @property
    @overrides(ApplicationVertex.n_atoms)
    def n_atoms(self):
        return self.__n_atoms

    @property
    def _neuron_recorder(self):  # for testing only
        return self.__neuron_recorder

    @inject_items({
        "graph": "MemoryApplicationGraph",
        "machine_time_step": "MachineTimeStep"
    })
    @overrides(ApplicationVertex.get_resources_used_by_atoms,
               additional_arguments={"graph", "machine_time_step"})
    def get_resources_used_by_atoms(self, vertex_slice, graph,
                                    machine_time_step):
        # pylint: disable=arguments-differ

        variableSDRAM = self.__neuron_recorder.get_variable_sdram_usage(
            vertex_slice)
        constantSDRAM = ConstantSDRAM(
            self._get_sdram_usage_for_atoms(vertex_slice, graph,
                                            machine_time_step))

        # set resources required from this object
        container = ResourceContainer(
            sdram=variableSDRAM + constantSDRAM,
            dtcm=DTCMResource(self.get_dtcm_usage_for_atoms(vertex_slice)),
            cpu_cycles=CPUCyclesPerTickResource(
                self.get_cpu_usage_for_atoms(vertex_slice)))

        # return the total resources.
        return container

    @property
    @overrides(AbstractChangableAfterRun.requires_mapping)
    def requires_mapping(self):
        return self.__change_requires_mapping

    @property
    @overrides(AbstractChangableAfterRun.requires_data_generation)
    def requires_data_generation(self):
        return self.__change_requires_data_generation

    @overrides(AbstractChangableAfterRun.mark_no_changes)
    def mark_no_changes(self):
        self.__change_requires_mapping = False
        self.__change_requires_data_generation = False

    @overrides(ApplicationVertex.create_machine_vertex)
    def create_machine_vertex(self,
                              vertex_slice,
                              resources_required,
                              label=None,
                              constraints=None):
        self.__n_subvertices += 1
        return PopulationMachineVertex(
            resources_required,
            self.__neuron_recorder.recorded_ids_by_slice(vertex_slice), label,
            constraints, self, vertex_slice)

    def get_cpu_usage_for_atoms(self, vertex_slice):
        """
        :param ~pacman.model.graphs.common.Slice vertex_slice:
        """
        return (_NEURON_BASE_N_CPU_CYCLES + _C_MAIN_BASE_N_CPU_CYCLES +
                (_NEURON_BASE_N_CPU_CYCLES_PER_NEURON * vertex_slice.n_atoms) +
                self.__neuron_recorder.get_n_cpu_cycles(vertex_slice.n_atoms) +
                self.__neuron_impl.get_n_cpu_cycles(vertex_slice.n_atoms) +
                self.__synapse_manager.get_n_cpu_cycles())

    def get_dtcm_usage_for_atoms(self, vertex_slice):
        """
        :param ~pacman.model.graphs.common.Slice vertex_slice:
        """
        return (
            _NEURON_BASE_DTCM_USAGE_IN_BYTES +
            self.__neuron_impl.get_dtcm_usage_in_bytes(vertex_slice.n_atoms) +
            self.__neuron_recorder.get_dtcm_usage_in_bytes(vertex_slice) +
            self.__synapse_manager.get_dtcm_usage_in_bytes())

    def _get_sdram_usage_for_neuron_params(self, vertex_slice):
        """ Calculate the SDRAM usage for just the neuron parameters region.

        :param ~pacman.model.graphs.common.Slice vertex_slice:
            the slice of atoms.
        :return: The SDRAM required for the neuron region
        """
        return (
            self._BYTES_TILL_START_OF_GLOBAL_PARAMETERS +
            self.__neuron_impl.get_sdram_usage_in_bytes(vertex_slice.n_atoms))

    def _get_sdram_usage_for_atoms(self, vertex_slice, graph,
                                   machine_time_step):
        sdram_requirement = (
            SYSTEM_BYTES_REQUIREMENT +
            self._get_sdram_usage_for_neuron_params(vertex_slice) +
            self._neuron_recorder.get_static_sdram_usage(vertex_slice) +
            PopulationMachineVertex.get_provenance_data_size(
                len(PopulationMachineVertex.EXTRA_PROVENANCE_DATA_ENTRIES)) +
            self.__synapse_manager.get_sdram_usage_in_bytes(
                vertex_slice, machine_time_step, graph, self) +
            profile_utils.get_profile_region_size(self.__n_profile_samples) +
            bit_field_utilities.get_estimated_sdram_for_bit_field_region(
                graph, self) +
            bit_field_utilities.get_estimated_sdram_for_key_region(
                graph, self) +
            bit_field_utilities.exact_sdram_for_bit_field_builder_region())
        return sdram_requirement

    def _reserve_memory_regions(self, spec, vertex_slice, vertex,
                                machine_graph, n_key_map):
        """ Reserve the DSG data regions.

        :param ~.DataSpecificationGenerator spec:
            the spec to write the DSG region to
        :param ~pacman.model.graphs.common.Slice vertex_slice:
            the slice of atoms from the application vertex
        :param ~.MachineVertex vertex: this vertex
        :param ~.MachineGraph machine_graph: machine graph
        :param n_key_map: nkey map
        :return: None
        """
        spec.comment("\nReserving memory space for data regions:\n\n")

        # Reserve memory:
        spec.reserve_memory_region(
            region=POPULATION_BASED_REGIONS.SYSTEM.value,
            size=common_constants.SIMULATION_N_BYTES,
            label='System')

        self._reserve_neuron_params_data_region(spec, vertex_slice)

        spec.reserve_memory_region(
            region=POPULATION_BASED_REGIONS.NEURON_RECORDING.value,
            size=self._neuron_recorder.get_static_sdram_usage(vertex_slice),
            label="neuron recording")

        profile_utils.reserve_profile_region(
            spec, POPULATION_BASED_REGIONS.PROFILING.value,
            self.__n_profile_samples)

        # reserve bit field region
        bit_field_utilities.reserve_bit_field_regions(
            spec, machine_graph, n_key_map, vertex,
            POPULATION_BASED_REGIONS.BIT_FIELD_BUILDER.value,
            POPULATION_BASED_REGIONS.BIT_FIELD_FILTER.value,
            POPULATION_BASED_REGIONS.BIT_FIELD_KEY_MAP.value)

        vertex.reserve_provenance_data_region(spec)

    def _reserve_neuron_params_data_region(self, spec, vertex_slice):
        """ Reserve the neuron parameter data region.

        :param ~data_specification.DataSpecificationGenerator spec:
            the spec to write the DSG region to
        :param ~pacman.model.graphs.common.Slice vertex_slice:
            the slice of atoms from the application vertex
        :return: None
        """
        params_size = self._get_sdram_usage_for_neuron_params(vertex_slice)
        spec.reserve_memory_region(
            region=POPULATION_BASED_REGIONS.NEURON_PARAMS.value,
            size=params_size,
            label='NeuronParams')

    @staticmethod
    def __copy_ranged_dict(source, merge=None, merge_keys=None):
        target = SpynnakerRangeDictionary(len(source))
        for key in source.keys():
            copy_list = SpynnakerRangedList(len(source))
            if merge_keys is None or key not in merge_keys:
                init_list = source.get_list(key)
            else:
                init_list = merge.get_list(key)
            for start, stop, value in init_list.iter_ranges():
                is_list = (hasattr(value, '__iter__')
                           and not isinstance(value, str))
                copy_list.set_value_by_slice(start, stop, value, is_list)
            target[key] = copy_list
        return target

    def _write_neuron_parameters(self, spec, key, vertex_slice,
                                 machine_time_step, time_scale_factor):

        # If resetting, reset any state variables that need to be reset
        if (self.__has_reset_last
                and self.__initial_state_variables is not None):
            self._state_variables = self.__copy_ranged_dict(
                self.__initial_state_variables, self._state_variables,
                self.__updated_state_variables)
            self.__initial_state_variables = None

        # If no initial state variables, copy them now
        if self.__has_reset_last:
            self.__initial_state_variables = self.__copy_ranged_dict(
                self._state_variables)

        # Reset things that need resetting
        self.__has_reset_last = False
        self.__updated_state_variables.clear()

        # pylint: disable=too-many-arguments
        n_atoms = vertex_slice.n_atoms
        spec.comment(
            "\nWriting Neuron Parameters for {} Neurons:\n".format(n_atoms))

        # Set the focus to the memory region 2 (neuron parameters):
        spec.switch_write_focus(
            region=constants.POPULATION_BASED_REGIONS.NEURON_PARAMS.value)

        # Write the random back off value
        max_offset = (machine_time_step *
                      time_scale_factor) // _MAX_OFFSET_DENOMINATOR
        spec.write_value(
            int(math.ceil(max_offset / self.__n_subvertices)) *
            self.__n_data_specs)
        self.__n_data_specs += 1

        # Write the number of microseconds between sending spikes
        time_between_spikes = ((machine_time_step * time_scale_factor) /
                               (n_atoms * 2.0))
        spec.write_value(data=int(time_between_spikes))

        # Write whether the key is to be used, and then the key, or 0 if it
        # isn't to be used
        if key is None:
            spec.write_value(data=0)
            spec.write_value(data=0)
        else:
            spec.write_value(data=1)
            spec.write_value(data=key)

        # Write the number of neurons in the block:
        spec.write_value(data=n_atoms)

        # Write the number of synapse types
        spec.write_value(data=self.__neuron_impl.get_n_synapse_types())

        # Write the size of the incoming spike buffer
        spec.write_value(data=self.__incoming_spike_buffer_size)

        # Write the neuron parameters
        neuron_data = self.__neuron_impl.get_data(self._parameters,
                                                  self._state_variables,
                                                  vertex_slice)
        spec.write_array(neuron_data)

    @inject_items({
        "machine_time_step": "MachineTimeStep",
        "time_scale_factor": "TimeScaleFactor",
        "routing_info": "MemoryRoutingInfos"
    })
    @overrides(AbstractRewritesDataSpecification.regenerate_data_specification,
               additional_arguments={
                   "machine_time_step", "time_scale_factor", "routing_info"
               })
    def regenerate_data_specification(self, spec, placement, machine_time_step,
                                      time_scale_factor, routing_info):
        # pylint: disable=too-many-arguments, arguments-differ
        vertex_slice = placement.vertex.vertex_slice

        # reserve the neuron parameters data region
        self._reserve_neuron_params_data_region(spec, vertex_slice)

        # write the neuron params into the new DSG region
        self._write_neuron_parameters(
            key=routing_info.get_first_key_from_pre_vertex(
                placement.vertex, constants.SPIKE_PARTITION_ID),
            machine_time_step=machine_time_step,
            spec=spec,
            time_scale_factor=time_scale_factor,
            vertex_slice=vertex_slice)

        # close spec
        spec.end_specification()

    @overrides(AbstractRewritesDataSpecification.
               requires_memory_regions_to_be_reloaded)
    def requires_memory_regions_to_be_reloaded(self):
        return self.__change_requires_neuron_parameters_reload

    @overrides(AbstractRewritesDataSpecification.mark_regions_reloaded)
    def mark_regions_reloaded(self):
        self.__change_requires_neuron_parameters_reload = False

    @inject_items({
        "machine_time_step": "MachineTimeStep",
        "time_scale_factor": "TimeScaleFactor",
        "application_graph": "MemoryApplicationGraph",
        "machine_graph": "MemoryMachineGraph",
        "routing_info": "MemoryRoutingInfos",
        "data_n_time_steps": "DataNTimeSteps",
        "n_key_map": "MemoryMachinePartitionNKeysMap"
    })
    @overrides(AbstractGeneratesDataSpecification.generate_data_specification,
               additional_arguments={
                   "machine_time_step", "time_scale_factor",
                   "application_graph", "machine_graph", "routing_info",
                   "data_n_time_steps", "n_key_map"
               })
    def generate_data_specification(self, spec, placement, machine_time_step,
                                    time_scale_factor, application_graph,
                                    machine_graph, routing_info,
                                    data_n_time_steps, n_key_map):
        """
        :param machine_time_step: (injected)
        :param time_scale_factor: (injected)
        :param application_graph: (injected)
        :param machine_graph: (injected)
        :param routing_info: (injected)
        :param data_n_time_steps: (injected)
        :param n_key_map: (injected)
        """
        # pylint: disable=too-many-arguments, arguments-differ
        vertex = placement.vertex

        spec.comment("\n*** Spec for block of {} neurons ***\n".format(
            self.__neuron_impl.model_name))
        vertex_slice = vertex.vertex_slice

        # Reserve memory regions
        self._reserve_memory_regions(spec, vertex_slice, vertex, machine_graph,
                                     n_key_map)

        # Declare random number generators and distributions:
        # TODO add random distribution stuff
        # self.write_random_distribution_declarations(spec)

        # Get the key
        key = routing_info.get_first_key_from_pre_vertex(
            vertex, constants.SPIKE_PARTITION_ID)

        # Write the setup region
        spec.switch_write_focus(POPULATION_BASED_REGIONS.SYSTEM.value)
        spec.write_array(
            simulation_utilities.get_simulation_header_array(
                self.get_binary_file_name(), machine_time_step,
                time_scale_factor))

        # Write the neuron recording region
        self._neuron_recorder.write_neuron_recording_region(
            spec, POPULATION_BASED_REGIONS.NEURON_RECORDING.value,
            vertex_slice, data_n_time_steps)

        # Write the neuron parameters
        self._write_neuron_parameters(spec, key, vertex_slice,
                                      machine_time_step, time_scale_factor)

        # write profile data
        profile_utils.write_profile_region_data(
            spec, POPULATION_BASED_REGIONS.PROFILING.value,
            self.__n_profile_samples)

        # Get the weight_scale value from the appropriate location
        weight_scale = self.__neuron_impl.get_global_weight_scale()

        # allow the synaptic matrix to write its data spec-able data
        self.__synapse_manager.write_data_spec(spec, self, vertex_slice,
                                               vertex, placement,
                                               machine_graph,
                                               application_graph, routing_info,
                                               weight_scale, machine_time_step)
        vertex.set_on_chip_generatable_area(
            self.__synapse_manager.host_written_matrix_size,
            self.__synapse_manager.on_chip_written_matrix_size)

        # write up the bitfield builder data
        bit_field_utilities.write_bitfield_init_data(
            spec, vertex, machine_graph, routing_info, n_key_map,
            POPULATION_BASED_REGIONS.BIT_FIELD_BUILDER.value,
            POPULATION_BASED_REGIONS.POPULATION_TABLE.value,
            POPULATION_BASED_REGIONS.SYNAPTIC_MATRIX.value,
            POPULATION_BASED_REGIONS.DIRECT_MATRIX.value,
            POPULATION_BASED_REGIONS.BIT_FIELD_FILTER.value,
            POPULATION_BASED_REGIONS.BIT_FIELD_KEY_MAP.value,
            POPULATION_BASED_REGIONS.STRUCTURAL_DYNAMICS.value,
            isinstance(self.__synapse_manager.synapse_dynamics,
                       AbstractSynapseDynamicsStructural))

        # End the writing of this specification:
        spec.end_specification()

    @overrides(AbstractHasAssociatedBinary.get_binary_file_name)
    def get_binary_file_name(self):

        # Split binary name into title and extension
        binary_title, binary_extension = os.path.splitext(
            self.__neuron_impl.binary_name)

        # Reunite title and extension and return
        return (binary_title +
                self.__synapse_manager.vertex_executable_suffix +
                binary_extension)

    @overrides(AbstractHasAssociatedBinary.get_binary_start_type)
    def get_binary_start_type(self):
        return ExecutableType.USES_SIMULATION_INTERFACE

    @overrides(AbstractSpikeRecordable.is_recording_spikes)
    def is_recording_spikes(self):
        return self.__neuron_recorder.is_recording(NeuronRecorder.SPIKES)

    @overrides(AbstractSpikeRecordable.set_recording_spikes)
    def set_recording_spikes(self,
                             new_state=True,
                             sampling_interval=None,
                             indexes=None):
        self.set_recording(NeuronRecorder.SPIKES, new_state, sampling_interval,
                           indexes)

    @overrides(AbstractSpikeRecordable.get_spikes)
    def get_spikes(self, placements, buffer_manager, machine_time_step):
        return self.__neuron_recorder.get_spikes(
            self.label, buffer_manager,
            len(self.__neuron_impl.get_recordable_variables()), placements,
            self, NeuronRecorder.SPIKES, machine_time_step)

    @overrides(AbstractNeuronRecordable.get_recordable_variables)
    def get_recordable_variables(self):
        return self.__neuron_recorder.get_recordable_variables()

    @overrides(AbstractNeuronRecordable.is_recording)
    def is_recording(self, variable):
        return self.__neuron_recorder.is_recording(variable)

    @overrides(AbstractNeuronRecordable.set_recording)
    def set_recording(self,
                      variable,
                      new_state=True,
                      sampling_interval=None,
                      indexes=None):
        self.__change_requires_mapping = not self.is_recording(variable)
        self.__neuron_recorder.set_recording(variable, new_state,
                                             sampling_interval, indexes)

    @overrides(AbstractNeuronRecordable.get_data)
    def get_data(self, variable, n_machine_time_steps, placements,
                 buffer_manager, machine_time_step):
        # pylint: disable=too-many-arguments
        return self.__neuron_recorder.get_matrix_data(
            self.label, buffer_manager,
            self.__neuron_impl.get_recordable_variable_index(variable),
            placements, self, variable, n_machine_time_steps)

    @overrides(AbstractNeuronRecordable.get_neuron_sampling_interval)
    def get_neuron_sampling_interval(self, variable):
        return self.__neuron_recorder.get_neuron_sampling_interval(variable)

    @overrides(AbstractSpikeRecordable.get_spikes_sampling_interval)
    def get_spikes_sampling_interval(self):
        return self.__neuron_recorder.get_neuron_sampling_interval("spikes")

    @overrides(AbstractPopulationInitializable.initialize)
    def initialize(self, variable, value):
        if not self.__has_reset_last:
            raise Exception(
                "initialize can only be called before the first call to run, "
                "or before the first call to run after a reset")
        if variable not in self._state_variables:
            raise KeyError("Vertex does not support initialisation of"
                           " parameter {}".format(variable))
        self._state_variables.set_value(variable, value)
        self.__updated_state_variables.add(variable)
        self.__change_requires_neuron_parameters_reload = True

    @property
    def initialize_parameters(self):
        """ The names of parameters that have default initial values.

        :rtype: iterable(str)
        """
        return self.__pynn_model.default_initial_values.keys()

    def _get_parameter(self, variable):
        if variable.endswith("_init"):
            # method called with "V_init"
            key = variable[:-5]
            if variable in self._state_variables:
                # variable is v and parameter is v_init
                return variable
            elif key in self._state_variables:
                # Oops neuron defines v and not v_init
                return key
        else:
            # method called with "v"
            if variable + "_init" in self._state_variables:
                # variable is v and parameter is v_init
                return variable + "_init"
            if variable in self._state_variables:
                # Oops neuron defines v and not v_init
                return variable

        # parameter not found for this variable
        raise KeyError("No variable {} found in {}".format(
            variable, self.__neuron_impl.model_name))

    @overrides(AbstractPopulationInitializable.get_initial_value)
    def get_initial_value(self, variable, selector=None):
        parameter = self._get_parameter(variable)

        ranged_list = self._state_variables[parameter]
        if selector is None:
            return ranged_list
        return ranged_list.get_values(selector)

    @overrides(AbstractPopulationInitializable.set_initial_value)
    def set_initial_value(self, variable, value, selector=None):
        if variable not in self._state_variables:
            raise KeyError("Vertex does not support initialisation of"
                           " parameter {}".format(variable))

        parameter = self._get_parameter(variable)
        ranged_list = self._state_variables[parameter]
        ranged_list.set_value_by_selector(selector, value)
        self.__change_requires_neuron_parameters_reload = True

    @property
    def conductance_based(self):
        """
        :rtype: bool
        """
        return self.__neuron_impl.is_conductance_based

    @overrides(AbstractPopulationSettable.get_value)
    def get_value(self, key):
        """ Get a property of the overall model.
        """
        if key not in self._parameters:
            raise InvalidParameterType(
                "Population {} does not have parameter {}".format(
                    self.__neuron_impl.model_name, key))
        return self._parameters[key]

    @overrides(AbstractPopulationSettable.set_value)
    def set_value(self, key, value):
        """ Set a property of the overall model.
        """
        if key not in self._parameters:
            raise InvalidParameterType(
                "Population {} does not have parameter {}".format(
                    self.__neuron_impl.model_name, key))
        self._parameters.set_value(key, value)
        self.__change_requires_neuron_parameters_reload = True

    @overrides(AbstractReadParametersBeforeSet.read_parameters_from_machine)
    def read_parameters_from_machine(self, transceiver, placement,
                                     vertex_slice):

        # locate SDRAM address to where the neuron parameters are stored
        neuron_region_sdram_address = \
            helpful_functions.locate_memory_region_for_placement(
                placement, POPULATION_BASED_REGIONS.NEURON_PARAMS.value,
                transceiver)

        # shift past the extra stuff before neuron parameters that we don't
        # need to read
        neuron_parameters_sdram_address = (
            neuron_region_sdram_address +
            self._BYTES_TILL_START_OF_GLOBAL_PARAMETERS)

        # get size of neuron params
        size_of_region = self._get_sdram_usage_for_neuron_params(vertex_slice)
        size_of_region -= self._BYTES_TILL_START_OF_GLOBAL_PARAMETERS

        # get data from the machine
        byte_array = transceiver.read_memory(placement.x, placement.y,
                                             neuron_parameters_sdram_address,
                                             size_of_region)

        # update python neuron parameters with the data
        self.__neuron_impl.read_data(byte_array, 0, vertex_slice,
                                     self._parameters, self._state_variables)

    @property
    def weight_scale(self):
        """
        :rtype: float
        """
        return self.__neuron_impl.get_global_weight_scale()

    @property
    def ring_buffer_sigma(self):
        return self.__synapse_manager.ring_buffer_sigma

    @ring_buffer_sigma.setter
    def ring_buffer_sigma(self, ring_buffer_sigma):
        self.__synapse_manager.ring_buffer_sigma = ring_buffer_sigma

    def reset_ring_buffer_shifts(self):
        self.__synapse_manager.reset_ring_buffer_shifts()

    @property
    def spikes_per_second(self):
        return self.__synapse_manager.spikes_per_second

    @spikes_per_second.setter
    def spikes_per_second(self, spikes_per_second):
        self.__synapse_manager.spikes_per_second = spikes_per_second

    @property
    def synapse_dynamics(self):
        """
        :rtype: AbstractSynapseDynamics
        """
        return self.__synapse_manager.synapse_dynamics

    def set_synapse_dynamics(self, synapse_dynamics):
        """
        :param AbstractSynapseDynamics synapse_dynamics:
        """
        self.__synapse_manager.synapse_dynamics = synapse_dynamics

    @overrides(AbstractAcceptsIncomingSynapses.add_pre_run_connection_holder)
    def add_pre_run_connection_holder(self, connection_holder, projection_edge,
                                      synapse_information):
        self.__synapse_manager.add_pre_run_connection_holder(
            connection_holder, projection_edge, synapse_information)

    def get_connection_holders(self):
        """
        :rtype: dict(tuple(ProjectionApplicationEdge,SynapseInformation),\
            ConnectionHolder)
        """
        return self.__synapse_manager.get_connection_holders()

    @overrides(AbstractAcceptsIncomingSynapses.get_connections_from_machine)
    def get_connections_from_machine(self,
                                     transceiver,
                                     placement,
                                     edge,
                                     routing_infos,
                                     synapse_information,
                                     machine_time_step,
                                     using_extra_monitor_cores,
                                     placements=None,
                                     monitor_api=None,
                                     fixed_routes=None,
                                     extra_monitor=None):
        # pylint: disable=too-many-arguments
        return self.__synapse_manager.get_connections_from_machine(
            transceiver, placement, edge, routing_infos, synapse_information,
            machine_time_step, using_extra_monitor_cores, placements,
            monitor_api, fixed_routes, extra_monitor)

    def clear_connection_cache(self):
        self.__synapse_manager.clear_connection_cache()

    def get_maximum_delay_supported_in_ms(self, machine_time_step):
        return self.__synapse_manager.get_maximum_delay_supported_in_ms(
            machine_time_step)

    @overrides(AbstractProvidesIncomingPartitionConstraints.
               get_incoming_partition_constraints)
    def get_incoming_partition_constraints(self, partition):
        """ Gets the constraints for partitions going into this vertex.

        :param partition: partition that goes into this vertex
        :return: list of constraints
        """
        return self.__synapse_manager.get_incoming_partition_constraints()

    @overrides(AbstractProvidesOutgoingPartitionConstraints.
               get_outgoing_partition_constraints)
    def get_outgoing_partition_constraints(self, partition):
        """ Gets the constraints for partitions going out of this vertex.

        :param partition: the partition that leaves this vertex
        :return: list of constraints
        """
        return [ContiguousKeyRangeContraint()]

    @overrides(AbstractNeuronRecordable.clear_recording)
    def clear_recording(self, variable, buffer_manager, placements):
        if variable == NeuronRecorder.SPIKES:
            index = len(self.__neuron_impl.get_recordable_variables())
        else:
            index = (
                self.__neuron_impl.get_recordable_variable_index(variable))
        self._clear_recording_region(buffer_manager, placements, index)

    @overrides(AbstractSpikeRecordable.clear_spike_recording)
    def clear_spike_recording(self, buffer_manager, placements):
        self._clear_recording_region(
            buffer_manager, placements,
            len(self.__neuron_impl.get_recordable_variables()))

    def _clear_recording_region(self, buffer_manager, placements,
                                recording_region_id):
        """ Clear a recorded data region from the buffer manager.

        :param buffer_manager: the buffer manager object
        :param placements: the placements object
        :param recording_region_id: the recorded region ID for clearing
        :rtype: None
        """
        for machine_vertex in self.machine_vertices:
            placement = placements.get_placement_of_vertex(machine_vertex)
            buffer_manager.clear_recorded_data(placement.x, placement.y,
                                               placement.p,
                                               recording_region_id)

    @overrides(AbstractContainsUnits.get_units)
    def get_units(self, variable):
        if variable == NeuronRecorder.SPIKES:
            return NeuronRecorder.SPIKES
        if self.__neuron_impl.is_recordable(variable):
            return self.__neuron_impl.get_recordable_units(variable)
        if variable not in self._parameters:
            raise Exception("Population {} does not have parameter {}".format(
                self.__neuron_impl.model_name, variable))
        return self.__neuron_impl.get_units(variable)

    def describe(self):
        """ Get a human-readable description of the cell or synapse type.

        The output may be customised by specifying a different template\
        together with an associated template engine\
        (see :py:mod:`pyNN.descriptions`).

        If template is None, then a dictionary containing the template context\
        will be returned.

        :rtype: dict(str, ...)
        """
        parameters = dict()
        for parameter_name in self.__pynn_model.default_parameters:
            parameters[parameter_name] = self.get_value(parameter_name)

        context = {
            "name": self.__neuron_impl.model_name,
            "default_parameters": self.__pynn_model.default_parameters,
            "default_initial_values": self.__pynn_model.default_parameters,
            "parameters": parameters,
        }
        return context

    def get_synapse_id_by_target(self, target):
        return self.__neuron_impl.get_synapse_id_by_target(target)

    def __str__(self):
        return "{} with {} atoms".format(self.label, self.n_atoms)

    def __repr__(self):
        return self.__str__()

    def gen_on_machine(self, vertex_slice):
        """ True if the synapses of a particular slice of this population \
            should be generated on the machine.

        :param ~pacman.model.graphs.common.Slice vertex_slice:
        """
        return self.__synapse_manager.gen_on_machine(vertex_slice)

    @overrides(AbstractCanReset.reset_to_first_timestep)
    def reset_to_first_timestep(self):
        # Mark that reset has been done, and reload state variables
        self.__has_reset_last = True
        self.__change_requires_neuron_parameters_reload = True

        # If synapses change during the run,
        if self.__synapse_manager.changes_during_run:
            self.__change_requires_data_generation = True
            self.__change_requires_neuron_parameters_reload = False
예제 #5
0
class SpiNNakEarApplicationVertex(
        ApplicationVertex, AbstractAcceptsIncomingSynapses,
        SimplePopulationSettable, HandOverToVertex, AbstractChangableAfterRun,
        AbstractSpikeRecordable, AbstractNeuronRecordable,
        AbstractControlsDestinationOfEdges, AbstractControlsSourceOfEdges,
        AbstractSendsOutgoingSynapses, AbstractCanReset, AbstractContainsUnits,
        AbstractApplicationSupportsAutoPauseAndResume,
        AbstractProvidesNKeysForPartition):

    __slots__ = [
        # pynn model
        '_model',
        # bool flag for neuron param changes
        '_remapping_required',
        # ihcan vertices
        "_ihcan_vertices",
        # drnl vertices
        "_drnl_vertices",
        # final agg verts (outgoing atoms)
        "_final_agg_vertices",
        # storing synapse dynamics
        "_synapse_dynamics",
        # fibres per.... something
        "_n_fibres_per_ihc",
        # the seed for the inner hair fibre
        "_ihcan_fibre_random_seed",
        # the number of columns / rows for aggregation tree
        "_n_group_tree_rows",
        # the synaptic manager to manage projections into drnl verts.
        "__synapse_manager",
        # the number of drnls there are.
        "_n_dnrls",
        # the number of agg verts which are final aggregation verts.
        "_n_final_agg_groups",
        # the pole frequencies
        "_pole_freqs",
        # The timer period for the fast components
        "_timer_period"
    ]

    # NOTES IHC = inner hair cell
    #       IHCan =  inner hair channel
    #       DRNL = middle ear filter
    #       OME ear fluid

    # error message for frequency
    FREQUENCY_ERROR = (
        "The input sampling frequency is too high for the chosen simulation "
        "time scale. Please reduce Fs or increase the time scale factor in "
        "the config file")

    # error message for get units
    GET_UNIT_ERROR = "do not know what to do with variable {} for get units"

    # error message if getting source outside aggregation verts
    PRE_SLICE_ERROR = (
        "Why are you asking for a source outside of aggregation verts?!")

    # error message if getting destination verts outside drnls.
    POST_SLICE_ERROR = (
        "why you asking for a destination atoms outside of the drnl verts!?")

    # error for processing plastic synapses
    PLASTIC_SYNAPSE_ERROR = (
        "The SpiNNaear cannot handle plastic synapses at the moment, "
        "complain to the SpiNNaker software team if this is a problem.")

    # error message for being asked to clear recording of a param we dont know
    CLEAR_RECORDING_ERROR = "Spinnakear does not support recording of {}"

    # error message for set recording of a variable we dont know about
    RECORDING_ERROR = "Spinnakear does not support recording of {}"

    # error message for sampling interval
    SAMPLING_INTERVAL_ERROR = "do not know how to handle variable {}"

    # error message for incorrect neurons map
    N_NEURON_ERROR = (
        "the number of neurons {} and the number of atoms  {} do not match")

    # app edge mc partition id
    MC_APP_EDGE_PARTITION_ID = "internal_mc"

    # app edge sdram partition id
    SDRAM_APP_EDGE_PARTITION_ID = "internal_sdram"

    # green wood function from https://en.wikipedia.org/wiki/Greenwood_function
    # constant below and mapped to variable names

    # green wood constants for human cochlea hearing frequency mapping
    # A is a scaling constant between the characteristic frequency and the
    # upper frequency limit of the species
    GREEN_WOOD_HUMAN_CONSTANT_A = 165.4

    # a is the slope of the straight-line portion of the frequency-position
    # curve, which has shown to be conserved throughout all investigated
    # species after scaling the length of the cochlea
    GREEN_WOOD_HUMAN_CONSTANT_ALPHA = 2.1

    # K is a constant of integration that represents the divergence from the
    # log nature of the curve and is determined by the lower frequency
    # audible limit in the species.
    GREEN_WOOD_HUMAN_CONSTANT_K = 0.88

    # n recording regions
    _N_POPULATION_RECORDING_REGIONS = 1

    # random numbers
    _FINAL_ROW_N_ATOMS = 256
    MAX_TIME_SCALE_FACTOR_RATIO = 22050

    # flags for sorting out random fibres. might be a enum
    HSR_FLAG = 2
    MSR_FLAG = 1
    LSR_FLAG = 0

    # how many synapse types this binary supports
    N_SYNAPSE_TYPES = 2

    # these curve values are built from profiling the IHCAN cores to deduce
    # performance.
    CURVE_ONE = 18.12
    CURVE_TWO = 10.99

    # max audio frequency supported
    DEFAULT_MAX_AUDIO_FREQUENCY = 20000

    # biggest number of neurons for the ear model
    FULL_EAR_HAIR_FIBERS = 30000.0

    # min audio frequency supported
    DEFAULT_MIN_AUDIO_FREQUENCY = 30

    def __init__(self, n_neurons, constraints, label, model, profile,
                 time_scale_factor):
        # Superclasses
        ApplicationVertex.__init__(self, label, constraints)
        AbstractAcceptsIncomingSynapses.__init__(self)
        SimplePopulationSettable.__init__(self)
        HandOverToVertex.__init__(self)
        AbstractChangableAfterRun.__init__(self)
        AbstractSpikeRecordable.__init__(self)
        AbstractNeuronRecordable.__init__(self)
        AbstractProvidesNKeysForPartition.__init__(self)

        self._model = model
        self._profile = profile
        self._remapping_required = True
        self._synapse_dynamics = None
        self._n_fibres_per_ihc = None
        self._n_group_tree_rows = None
        self._ihcan_vertices = list()
        self._drnl_vertices = list()
        self._final_agg_vertices = list()
        self.__synapse_manager = SynapticManager(
            self.N_SYNAPSE_TYPES, None, None,
            globals_variables.get_simulator().config)

        # calculate n fibres per ihcan core
        sample_time = time_scale_factor / self._model.fs

        # how many channels
        self._n_channels = int(self.get_out_going_size() /
                               self._model.n_fibres_per_ihc)

        # process pole freqs
        self._pole_freqs = self._process_pole_freqs()

        # how many fibres / atoms ran on each ihcan core
        self._n_fibres_per_ihcan_core = self.fibres_per_ihcan_core(
            sample_time, self._model.n_fibres_per_ihc)

        # process all the other internal numbers
        atoms_per_row = self.process_internal_numbers()

        # read in param file if needed
        self._process_param_file(atoms_per_row)

        # recording stuff
        self._drnl_neuron_recorder = NeuronRecorder(
            DRNLMachineVertex.RECORDABLES,
            DRNLMachineVertex.get_matrix_scalar_data_types(),
            DRNLMachineVertex.get_matrix_output_data_types(), self._n_dnrls)

        self._ihcan_neuron_recorder = NeuronRecorder(
            IHCANMachineVertex.RECORDABLES,
            IHCANMachineVertex.get_matrix_scalar_data_types(),
            IHCANMachineVertex.get_matrix_output_data_types(),
            self._n_dnrls * self._n_fibres_per_ihc)

        # bool for if state has changed.
        self._change_requires_mapping = True
        self._change_requires_neuron_parameters_reload = False
        self._change_requires_data_generation = False
        self._has_reset_last = True

        # safety check
        if self._n_atoms != n_neurons:
            raise ConfigurationException(
                self.N_NEURON_ERROR.format(n_neurons, self._n_atoms))

        # safety stuff
        if (self._model.fs / time_scale_factor >
                self.MAX_TIME_SCALE_FACTOR_RATIO):
            raise Exception(self.FREQUENCY_ERROR)

        # write timer period
        self._timer_period = (MICRO_TO_SECOND_CONVERSION *
                              (self._model.seq_size / self._model.fs))

    @overrides(AbstractProvidesNKeysForPartition.get_n_keys_for_partition)
    def get_n_keys_for_partition(self, partition, graph_mapper):
        return partition.pre_vertex.get_n_keys_for_partition(
            partition, graph_mapper)

    @overrides(AbstractNeuronRecordable.get_expected_n_rows)
    def get_expected_n_rows(self, current_run_timesteps_map, sampling_rate,
                            vertex, variable):
        if isinstance(vertex, DRNLMachineVertex):
            return int(
                (self._drnl_neuron_recorder.expected_rows_for_a_run_time(
                    current_run_timesteps_map, vertex, sampling_rate)) *
                self._model.seq_size)
        else:
            return int(
                self._ihcan_neuron_recorder.expected_rows_for_a_run_time(
                    current_run_timesteps_map, vertex, sampling_rate))

    @staticmethod
    def fibres_per_ihcan_core(sample_time, n_fibres_per_ihc):
        # how many fibras / atoms ran on each ihcan core
        max_possible = abs(
            int(
                math.floor(((sample_time * MICRO_TO_SECOND_CONVERSION) -
                            SpiNNakEarApplicationVertex.CURVE_ONE) /
                           SpiNNakEarApplicationVertex.CURVE_TWO)))
        return min(n_fibres_per_ihc, max_possible, 2)

    @overrides(AbstractAcceptsIncomingSynapses.gen_on_machine)
    def gen_on_machine(self, vertex_slice):
        return self.__synapse_manager.gen_on_machine(vertex_slice)

    @overrides(AbstractApplicationSupportsAutoPauseAndResume.
               my_variable_local_time_period)
    def my_variable_local_time_period(self, default_machine_time_step,
                                      variable):
        if variable == DRNLMachineVertex.MOC:
            return default_machine_time_step
        else:
            return self._timer_period

    def reset_to_first_timestep(self):
        # Mark that reset has been done, and reload state variables
        self._has_reset_last = True
        self._change_requires_neuron_parameters_reload = False

        # If synapses change during the run,
        if self._synapse_manager.synapse_dynamics.changes_during_run:
            self._change_requires_data_generation = True

    def get_units(self, variable):
        if variable in DRNLMachineVertex.RECORDABLES:
            return DRNLMachineVertex.RECORDABLE_UNITS[variable]
        elif variable in IHCANMachineVertex.RECORDABLES:
            return IHCANMachineVertex.RECORDABLE_UNITS[variable]
        else:
            raise Exception(self.GET_UNIT_ERROR.format(variable))

    def _process_param_file(self, atoms_per_row):
        if self._model.param_file is not None:
            try:
                pre_gen_vars = numpy.load(self._model.param_file)
                self._n_atoms = pre_gen_vars['n_atoms']
                self._mv_index_list = pre_gen_vars['mv_index_list']
                self._parent_index_list = pre_gen_vars['parent_index_list']
                self._edge_index_list = pre_gen_vars['edge_index_list']
                self._ihc_seeds = pre_gen_vars['ihc_seeds']
                self._ome_indices = pre_gen_vars['ome_indices']
            except Exception:
                self._n_atoms, self._n_dnrls, self._n_final_agg_groups = \
                    self.calculate_n_atoms_for_each_vertex_type(
                        atoms_per_row, self._n_channels,
                        self._model.n_fibres_per_ihc, self._model.seq_size)
                # save fixed param file
                self._save_pre_gen_vars(self._model.param_file)
        else:
            self._n_atoms, self._n_dnrls, self._n_final_agg_groups = \
                self.calculate_n_atoms_for_each_vertex_type(
                    atoms_per_row, self._n_channels,
                    self._model.n_fibres_per_ihc, self._model.seq_size)

    def process_internal_numbers(self):

        # ear hair frequency bits in total per inner ear channel
        self._n_fibres_per_ihc = (self._model.n_lsr_per_ihc +
                                  self._model.n_msr_per_ihc +
                                  self._model.n_hsr_per_ihc)

        # number of columns needed for the aggregation tree
        atoms_per_row = self.calculate_atoms_per_row(
            self._n_channels, self._n_fibres_per_ihc,
            self._n_fibres_per_ihcan_core,
            self._model.max_input_to_aggregation_group)

        # if no rows, then just add 1 row with 1 vertex.
        if atoms_per_row == 0:
            self._n_group_tree_rows = 1
            return self._n_fibres_per_ihcan_core
        else:

            # figure how many atoms per aggregation element per row
            max_n_atoms_per_group_tree_row = (
                (self._model.max_input_to_aggregation_group**numpy.arange(
                    1, atoms_per_row + 1)) * self._n_fibres_per_ihcan_core)

            # filter rows max atoms so that its capped at 256
            max_n_atoms_per_group_tree_row = \
                max_n_atoms_per_group_tree_row[
                    max_n_atoms_per_group_tree_row <= min(
                        self._FINAL_ROW_N_ATOMS,
                        self._n_channels * self._n_fibres_per_ihc)]

            self._n_group_tree_rows = max_n_atoms_per_group_tree_row.size
        return atoms_per_row

    def _process_pole_freqs(self):
        if self._model.pole_freqs is None:
            if self._model.fs > 2 * self.DEFAULT_MAX_AUDIO_FREQUENCY:  # use
                # the greenwood mapping
                pole_freqs = (numpy.flipud([
                    self.GREEN_WOOD_HUMAN_CONSTANT_A *
                    (10**(self.GREEN_WOOD_HUMAN_CONSTANT_ALPHA *
                          numpy.linspace([0], [1], self._n_channels)) -
                     self.GREEN_WOOD_HUMAN_CONSTANT_K)
                ]))

            # don't want alias frequencies so we use a capped log scale map
            else:
                max_power = min([
                    numpy.log10(self.fs / 2.),
                    numpy.log10(self.DEFAULT_MAX_AUDIO_FREQUENCY)
                ])
                pole_freqs = numpy.flipud(
                    numpy.logspace(
                        numpy.log10(self.DEFAULT_MIN_AUDIO_FREQUENCY),
                        max_power, self._n_channels))
        else:
            pole_freqs = self._model.pole_freqs
        return pole_freqs[0]

    @overrides(AbstractSendsOutgoingSynapses.get_out_going_size)
    def get_out_going_size(self):
        return (int(self.FULL_EAR_HAIR_FIBERS * float(self._model.scale) /
                    self._model.n_fibres_per_ihc) *
                self._model.n_fibres_per_ihc)

    @overrides(AbstractControlsSourceOfEdges.get_out_going_slices)
    def get_out_going_slices(self):
        slices = list()
        starter = 0
        for agg_vertex in self._final_agg_vertices:
            slices.append(agg_vertex.connection_slice)
            starter += agg_vertex.n_atoms
        return slices

    @overrides(AbstractControlsDestinationOfEdges.get_in_coming_slices)
    def get_in_coming_slices(self):
        slices = list()
        starter = 0
        for _ in self._drnl_vertices:
            slices.append(Slice(starter, starter))
            starter += 1
        return slices

    @overrides(AbstractControlsSourceOfEdges.get_pre_slice_for)
    def get_pre_slice_for(self, machine_vertex):
        if isinstance(machine_vertex, ANGroupMachineVertex):
            if machine_vertex.is_final_row:
                return machine_vertex.connection_slice
        raise Exception(self.PRE_SLICE_ERROR)

    @overrides(AbstractControlsDestinationOfEdges.get_post_slice_for)
    def get_post_slice_for(self, machine_vertex):
        if isinstance(machine_vertex, DRNLMachineVertex):
            return Slice(machine_vertex.drnl_index, machine_vertex.drnl_index)
        raise Exception(self.POST_SLICE_ERROR)

    @overrides(AbstractAcceptsIncomingSynapses.get_in_coming_size)
    def get_in_coming_size(self):
        return self._n_dnrls

    @overrides(AbstractAcceptsIncomingSynapses.get_synapse_id_by_target)
    def get_synapse_id_by_target(self, target):
        if target == "excitatory":
            return 0
        elif target == "inhibitory":
            return 1
        return None

    @overrides(
        AbstractControlsDestinationOfEdges.get_destinations_for_edge_from)
    def get_destinations_for_edge_from(self, app_edge, partition_id,
                                       graph_mapper,
                                       original_source_machine_vertex):
        if ((app_edge.pre_vertex != self and app_edge.post_vertex == self)
                and not isinstance(original_source_machine_vertex,
                                   OMEMachineVertex)):
            return self._drnl_vertices
        else:
            return []

    @overrides(AbstractControlsSourceOfEdges.get_sources_for_edge_from)
    def get_sources_for_edge_from(self, app_edge, partition_id, graph_mapper,
                                  original_source_machine_vertex):
        if ((app_edge.pre_vertex == self and app_edge.post_vertex != self)
                and isinstance(original_source_machine_vertex,
                               ANGroupMachineVertex)
                and original_source_machine_vertex.is_final_row):
            return [original_source_machine_vertex]
        else:
            return []

    @overrides(
        AbstractAcceptsIncomingSynapses.get_maximum_delay_supported_in_ms)
    def get_maximum_delay_supported_in_ms(self, default_machine_time_step):
        return self.__synapse_manager.get_maximum_delay_supported_in_ms(
            default_machine_time_step)

    @overrides(AbstractAcceptsIncomingSynapses.add_pre_run_connection_holder)
    def add_pre_run_connection_holder(self, connection_holder, projection_edge,
                                      synapse_information):
        self.__synapse_manager.add_pre_run_connection_holder(
            connection_holder, projection_edge, synapse_information)

    def _save_pre_gen_vars(self, file_path):
        """ saves params into a numpy file.
        :param file_path: path to file to store stuff into
        :rtype: None
        """
        numpy.savez_compressed(file_path,
                               n_atoms=self._n_atoms,
                               mv_index_list=self._mv_index_list,
                               parent_index_list=self._parent_index_list,
                               edge_index_list=self._edge_index_list,
                               ihc_seeds=self._ihc_seeds,
                               ome_indices=self._ome_indices)

    @overrides(AbstractAcceptsIncomingSynapses.set_synapse_dynamics)
    def set_synapse_dynamics(self, synapse_dynamics):
        if not isinstance(synapse_dynamics, SynapseDynamicsStatic):
            raise Exception(self.PLASTIC_SYNAPSE_ERROR)
        self._synapse_dynamics = synapse_dynamics

    @overrides(AbstractAcceptsIncomingSynapses.get_connections_from_machine)
    def get_connections_from_machine(self,
                                     transceiver,
                                     placement,
                                     edge,
                                     graph_mapper,
                                     routing_infos,
                                     synapse_information,
                                     local_time_step_map,
                                     using_extra_monitor_cores,
                                     placements=None,
                                     monitor_api=None,
                                     monitor_placement=None,
                                     monitor_cores=None,
                                     handle_time_out_configuration=True,
                                     fixed_routes=None):
        return self.__synapse_manager.get_connections_from_machine(
            transceiver, placement, edge, graph_mapper, routing_infos,
            synapse_information, local_time_step_map,
            using_extra_monitor_cores,
            DRNLMachineVertex.REGIONS.POPULATION_TABLE.value,
            DRNLMachineVertex.REGIONS.SYNAPTIC_MATRIX.value,
            DRNLMachineVertex.REGIONS.DIRECT_MATRIX.value, placements,
            monitor_api, monitor_placement, monitor_cores,
            handle_time_out_configuration, fixed_routes)

    @overrides(AbstractAcceptsIncomingSynapses.clear_connection_cache)
    def clear_connection_cache(self):
        self.__synapse_manager.clear_connection_cache()

    @overrides(SimplePopulationSettable.set_value)
    def set_value(self, key, value):
        SimplePopulationSettable.set_value(self, key, value)
        self._remapping_required = True

    def describe(self):
        """ Returns a human-readable description of the cell or synapse type.

        The output may be customised by specifying a different template\
        together with an associated template engine\
        (see ``pyNN.descriptions``).

        If template is None, then a dictionary containing the template\
        context will be returned.
        """

        parameters = dict()
        for parameter_name in self._model.default_parameters:
            parameters[parameter_name] = self.get_value(parameter_name)

        context = {
            "name": self._model.model_name,
            "default_parameters": self._model.default_parameters,
            "default_initial_values": self._model.default_parameters,
            "parameters": parameters,
        }
        return context

    @overrides(AbstractPopulationSettable.get_value)
    def get_value(self, key):
        if hasattr(self._model, key):
            return getattr(self._model, key)
        raise Exception("Population {} does not have parameter {}".format(
            self, key))

    def _add_to_graph_components(self, machine_graph, graph_mapper, slice,
                                 vertex, resource_tracker):
        """ adds the vertex to all the graph components and resources

        :param machine_graph: machine graph
        :param graph_mapper: graph mapper
        :param slice: slice
        :param vertex: machien vertex
        :param resource_tracker: resource tracker
        :rtype: None
        """

        machine_graph.add_vertex(vertex)
        graph_mapper.add_vertex_mapping(vertex, slice, self)
        resource_tracker.allocate_constrained_resources(
            vertex.resources_required, vertex.constraints)

    def _build_ome_vertex(self, machine_graph, graph_mapper, lo_atom,
                          resource_tracker, timer_period):
        """ builds the ome vertex

        :param machine_graph: machine graph
        :param graph_mapper: graph mapper
        :param lo_atom: lo atom to put into graph mapper slice
        :param resource_tracker: the resource tracker
        :param timer_period: the timer period for all machine verts based on\
        the ear vertex
        :return: the ome vertex and the new low atom
        """
        # build the ome machine vertex
        ome_vertex = OMEMachineVertex(self._model.audio_input, self._model.fs,
                                      self._n_channels, self._model.seq_size,
                                      timer_period, self._profile)

        # allocate resources and updater graphs
        self._add_to_graph_components(machine_graph, graph_mapper,
                                      Slice(lo_atom, lo_atom), ome_vertex,
                                      resource_tracker)
        return ome_vertex, lo_atom + 1

    def _build_drnl_verts(self, machine_graph, graph_mapper, new_low_atom,
                          resource_tracker, ome_vertex, timer_period):
        """ build the drnl verts

        :param machine_graph: machine graph
        :param graph_mapper: graph mapper
        :param new_low_atom: the current low atom count for the graph mapper
        :param resource_tracker: the resource tracker for placement
        :param ome_vertex: the ome vertex to tie edges to
        :param timer_period: the timer period for all machine verts based on\
        the ear vertex
        :return: new low atom count
        """
        pole_index = 0
        for _ in range(self._n_channels):
            drnl_vertex = DRNLMachineVertex(
                self._pole_freqs[pole_index], self._model.fs,
                ome_vertex.n_data_points, pole_index, self._profile,
                self._model.seq_size, self.__synapse_manager, self,
                self._model.n_buffers_in_sdram_total,
                self._drnl_neuron_recorder, timer_period)
            pole_index += 1
            self._add_to_graph_components(machine_graph, graph_mapper,
                                          Slice(new_low_atom, new_low_atom),
                                          drnl_vertex, resource_tracker)
            new_low_atom += 1
            self._drnl_vertices.append(drnl_vertex)
        return new_low_atom

    def _build_edges_between_ome_drnls(self, ome_vertex, machine_graph,
                                       app_edge, graph_mapper):
        """ adds edges between the ome and the drnl vertices

        :param ome_vertex: the ome vertex
        :param machine_graph: the machine graph
        :param app_edge: the app edge covering all these edges
        :param graph_mapper: the graph mapper
        :rtype: None
        """
        for drnl_vert in self._drnl_vertices:
            edge = SpiNNakEarMachineEdge(ome_vertex, drnl_vert)
            machine_graph.add_edge(edge, ome_vertex.OME_PARTITION_ID)
            graph_mapper.add_edge_mapping(edge, app_edge)

    def _build_ihcan_vertices_and_sdram_edges(self, machine_graph,
                                              graph_mapper, new_low_atom,
                                              resource_tracker, app_edge,
                                              sdram_app_edge, timer_period):
        """ builds the ihcan verts and adds edges from drnl to them

        :param machine_graph: machine graph
        :param graph_mapper: the graph mapper
        :param new_low_atom: the lo atom sued to keep the graph mapper happy
        :param resource_tracker: the resource tracker for placement
        :param app_edge: the app edge to link all mc machine edges to
        :param sdram_app_edge: the application sdram edge between drnl and \
        inchan to link all sdram machine edges to.
        :param timer_period: the timer period for all machine verts based on\
        the ear vertex
        :return: iterable of ihcan verts
        """

        ihcans = list()

        # generate ihc seeds
        n_ihcans = self._n_channels * self._model.n_fibres_per_ihc
        seed_index = 0
        random_range = numpy.arange(
            n_ihcans * IHCANMachineVertex.N_SEEDS_PER_IHCAN_VERTEX,
            dtype=numpy.uint32)
        numpy.random.seed(self._model.ihc_seeds_seed)
        ihc_seeds = numpy.random.choice(
            random_range,
            int(n_ihcans * IHCANMachineVertex.N_SEEDS_PER_IHCAN_VERTEX),
            replace=False)

        ihcan_recording_index = 0

        for drnl_vertex in self._drnl_vertices:
            machine_graph.add_outgoing_edge_partition(
                ConstantSDRAMMachinePartition(
                    drnl_vertex.DRNL_SDRAM_PARTITION_ID, drnl_vertex,
                    "sdram edge between drnl vertex {} and its "
                    "IHCANS".format(drnl_vertex.drnl_index)))

            fibres = []
            for _ in range(self._model.n_hsr_per_ihc):
                fibres.append(self.HSR_FLAG)
            for __ in range(self._model.n_msr_per_ihc):
                fibres.append(self.MSR_FLAG)
            for ___ in range(self._model.n_lsr_per_ihc):
                fibres.append(self.LSR_FLAG)

            random.seed(self._model.ihcan_fibre_random_seed)
            random.shuffle(fibres)

            for _ in range(
                    int(self._model.n_fibres_per_ihc /
                        self._n_fibres_per_ihcan_core)):

                # randomly pick fibre types
                chosen_indices = [
                    fibres.pop() for _ in range(self._n_fibres_per_ihcan_core)
                ]

                ihcan_slice = Slice(
                    new_low_atom, new_low_atom +
                    (self._n_fibres_per_ihcan_core * self._model.seq_size) - 1)

                ihcan_recording_slice = Slice(
                    ihcan_recording_index, ihcan_recording_index +
                    (self._n_fibres_per_ihcan_core * self._model.seq_size) - 1)
                ihcan_recording_index += ihcan_recording_slice.n_atoms

                vertex = IHCANMachineVertex(
                    self._model.resample_factor,
                    ihc_seeds[seed_index:seed_index +
                              IHCANMachineVertex.N_SEEDS_PER_IHCAN_VERTEX],
                    self._n_fibres_per_ihcan_core, self._model.ear_index,
                    self._profile, self._model.fs,
                    chosen_indices.count(self.LSR_FLAG),
                    chosen_indices.count(self.MSR_FLAG),
                    chosen_indices.count(self.HSR_FLAG),
                    self._model.n_buffers_in_sdram_total, self._model.seq_size,
                    self._ihcan_neuron_recorder, ihcan_recording_slice,
                    timer_period)

                # update indexes
                new_low_atom += ihcan_slice.n_atoms
                seed_index += IHCANMachineVertex.N_SEEDS_PER_IHCAN_VERTEX

                # add to list of ihcans
                ihcans.append(vertex)

                self._add_to_graph_components(machine_graph, graph_mapper,
                                              ihcan_slice, vertex,
                                              resource_tracker)

                # multicast
                mc_edge = SpiNNakEarMachineEdge(drnl_vertex, vertex,
                                                EdgeTrafficType.MULTICAST)
                machine_graph.add_edge(mc_edge, drnl_vertex.DRNL_PARTITION_ID)
                graph_mapper.add_edge_mapping(mc_edge, app_edge)

                # sdram edge
                sdram_edge = SDRAMMachineEdge(
                    drnl_vertex, vertex, drnl_vertex.sdram_edge_size,
                    "sdram between {} and {}".format(drnl_vertex, vertex))
                machine_graph.add_edge(sdram_edge,
                                       drnl_vertex.DRNL_SDRAM_PARTITION_ID)
                graph_mapper.add_edge_mapping(sdram_edge, sdram_app_edge)
        return ihcans, new_low_atom

    def _build_aggregation_group_vertices_and_edges(self, machine_graph,
                                                    graph_mapper, new_low_atom,
                                                    resource_tracker,
                                                    app_edge):

        to_process = list()
        to_process.extend(self._ihcan_vertices)
        n_child_per_group = self._model.max_input_to_aggregation_group

        for row in range(self._n_group_tree_rows):
            aggregation_verts = list()
            n_row_angs = int(
                numpy.ceil(float(len(to_process)) / n_child_per_group))
            for an in range(n_row_angs):
                final_row_lo_atom = 0
                child_verts = to_process[an * n_child_per_group:an *
                                         n_child_per_group + n_child_per_group]

                # deduce n atoms of the ag node
                n_atoms = 0
                for child in child_verts:
                    n_atoms += child.n_atoms

                # build silce for an node
                an_slice = Slice(new_low_atom, new_low_atom + n_atoms - 1)
                new_low_atom += n_atoms

                # build vert
                final_row = row == self._n_group_tree_rows - 1

                final_row_slice = None
                if final_row:
                    final_row_slice = Slice(final_row_lo_atom,
                                            final_row_lo_atom + n_atoms)
                    final_row_lo_atom += n_atoms

                ag_vertex = ANGroupMachineVertex(n_atoms, len(child_verts),
                                                 final_row, row,
                                                 self._model.ear_index,
                                                 final_row_slice)

                # only store it in the agg array if its in the final row
                if final_row:
                    self._final_agg_vertices.append(ag_vertex)

                # store for the next cycle
                aggregation_verts.append(ag_vertex)

                # update stuff
                self._add_to_graph_components(machine_graph, graph_mapper,
                                              an_slice, ag_vertex,
                                              resource_tracker)

                # add edges
                for child_vert in child_verts:
                    # sort out partition id
                    partition_id = IHCANMachineVertex.IHCAN_PARTITION_ID
                    if isinstance(child_vert, ANGroupMachineVertex):
                        partition_id = \
                            ANGroupMachineVertex.AN_GROUP_PARTITION_IDENTIFIER

                    # add edge and mapping
                    mc_edge = SpiNNakEarMachineEdge(child_vert, ag_vertex)
                    machine_graph.add_edge(mc_edge, partition_id)
                    graph_mapper.add_edge_mapping(mc_edge, app_edge)

            to_process = aggregation_verts

    @inject_items({"application_graph": "MemoryApplicationGraph"})
    @overrides(HandOverToVertex.create_and_add_to_graphs_and_resources,
               additional_arguments={"application_graph"})
    def create_and_add_to_graphs_and_resources(self, resource_tracker,
                                               machine_graph, graph_mapper,
                                               application_graph):

        mc_app_edge = ApplicationEdge(self, self)
        sdram_app_edge = ApplicationEdge(self, self, EdgeTrafficType.SDRAM)
        application_graph.add_edge(mc_app_edge, self.MC_APP_EDGE_PARTITION_ID)
        application_graph.add_edge(sdram_app_edge,
                                   self.SDRAM_APP_EDGE_PARTITION_ID)

        # atom tracker
        current_atom_count = 0

        timer_period = (MICRO_TO_SECOND_CONVERSION * self._model.seq_size /
                        self._model.fs)

        # ome vertex
        ome_vertex, current_atom_count = self._build_ome_vertex(
            machine_graph, graph_mapper, current_atom_count, resource_tracker,
            timer_period)

        # handle the drnl verts
        current_atom_count = self._build_drnl_verts(machine_graph,
                                                    graph_mapper,
                                                    current_atom_count,
                                                    resource_tracker,
                                                    ome_vertex, timer_period)

        # handle edges between ome and drnls
        self._build_edges_between_ome_drnls(ome_vertex, machine_graph,
                                            mc_app_edge, graph_mapper)

        # build the ihcan verts.
        self._ihcan_vertices, current_atom_count = (
            self._build_ihcan_vertices_and_sdram_edges(
                machine_graph, graph_mapper, current_atom_count,
                resource_tracker, mc_app_edge, sdram_app_edge, timer_period))

        # build aggregation group verts and edges
        self._build_aggregation_group_vertices_and_edges(
            machine_graph, graph_mapper, current_atom_count, resource_tracker,
            mc_app_edge)

    @property
    @overrides(ApplicationVertex.n_atoms)
    def n_atoms(self):
        return self._n_atoms

    @staticmethod
    def calculate_n_atoms_for_each_vertex_type(n_group_tree_rows, n_channels,
                                               n_ihc, seq_size):
        # ome atom
        n_atoms = 1

        # dnrl atoms
        n_atoms += n_channels

        # ihcan atoms
        n_angs = n_channels * n_ihc
        n_atoms += (n_angs * seq_size)

        # an group atoms
        for row_index in range(n_group_tree_rows):
            n_atoms += n_angs

        return n_atoms, n_channels, n_angs

    @staticmethod
    def calculate_atoms_per_row(n_channels, n_fibres_per_ihc,
                                n_fibres_per_ihcan,
                                max_input_to_aggregation_group):
        return math.ceil(
            numpy.ceil(
                math.log((n_channels * n_fibres_per_ihc) / n_fibres_per_ihcan,
                         max_input_to_aggregation_group)))

    @overrides(AbstractChangableAfterRun.mark_no_changes)
    def mark_no_changes(self):
        self._remapping_required = False

    @property
    @overrides(AbstractChangableAfterRun.requires_mapping)
    def requires_mapping(self):
        return self._remapping_required

    @overrides(AbstractSpikeRecordable.is_recording_spikes)
    def is_recording_spikes(self):
        return self._ihcan_neuron_recorder.is_recording(
            IHCANMachineVertex.SPIKES)

    @overrides(AbstractSpikeRecordable.get_spikes_sampling_interval)
    def get_spikes_sampling_interval(self, graph_mapper,
                                     local_time_period_map):
        if graph_mapper is None or local_time_period_map is None:
            return self.my_variable_local_time_period(
                get_simulator().default_machine_time_step,
                IHCANMachineVertex.SPIKES)
        return self._ihcan_neuron_recorder.get_neuron_sampling_interval(
            IHCANMachineVertex.SPIKES, self._ihcan_vertices[0],
            local_time_period_map)

    @overrides(AbstractSpikeRecordable.clear_spike_recording)
    def clear_spike_recording(self, buffer_manager, placements, graph_mapper):
        for ihcan_vertex in self._ihcan_vertices:
            placement = placements.get_placement_of_vertex(ihcan_vertex)
            buffer_manager.clear_recorded_data(
                placement.x, placement.y, placement.p,
                IHCANMachineVertex.SPIKE_RECORDING_REGION_ID)

    @overrides(AbstractSpikeRecordable.get_spikes)
    def get_spikes(self, placements, graph_mapper, buffer_manager,
                   local_timer_period_map):
        return self._ihcan_neuron_recorder.get_spikes(
            self._label, buffer_manager, IHCANMachineVertex.RECORDING_REGIONS.
            SPIKE_RECORDING_REGION_ID.value, placements, graph_mapper, self,
            local_timer_period_map)

    @overrides(AbstractSpikeRecordable.set_recording_spikes)
    def set_recording_spikes(self,
                             default_machine_time_step,
                             new_state=True,
                             sampling_interval=None,
                             indexes=None):
        self.set_recording(IHCANMachineVertex.SPIKES, self._timer_period,
                           new_state, sampling_interval, indexes)

    @overrides(AbstractNeuronRecordable.get_recordable_variables)
    def get_recordable_variables(self):
        recordables = list()
        # don't take the drnl spikes, as there's only 1 api for spikes, and the
        # drnl spikes are only there for the recording limitations
        recordables.append(DRNLMachineVertex.MOC)
        recordables.extend(IHCANMachineVertex.RECORDABLES)
        return recordables

    @overrides(AbstractSpikeRecordable.get_spike_machine_vertices)
    def get_spike_machine_vertices(self, graph_mapper):
        return self._ihcan_vertices

    @overrides(AbstractNeuronRecordable.get_machine_vertices_for)
    def get_machine_vertices_for(self, variable, graph_mapper):
        if variable == DRNLMachineVertex.MOC:
            return self._drnl_vertices
        else:
            return self._ihcan_vertices

    @overrides(AbstractNeuronRecordable.clear_recording)
    def clear_recording(self, variable, buffer_manager, placements,
                        graph_mapper):
        if variable == DRNLMachineVertex.MOC:
            for drnl_vertex in self._drnl_vertices:
                placement = placements.get_placement_of_vertex(drnl_vertex)
                buffer_manager.clear_recorded_data(
                    placement.x, placement.y, placement.p,
                    DRNLMachineVertex.MOC_RECORDING_REGION_ID.value)
        if variable == self.SPIKES:
            for ihcan_vertex in self._ihcan_vertices:
                placement = placements.get_placement_of_vertex(ihcan_vertex)
                buffer_manager.clear_recorded_data(
                    placement.x, placement.y, placement.p,
                    (IHCANMachineVertex.RECORDING_REGIONS.
                     SPIKE_RECORDING_REGION_ID.value))
        if variable == self.SPIKE_PROB:
            for ihcan_vertex in self._ihcan_vertices:
                placement = placements.get_placement_of_vertex(ihcan_vertex)
                buffer_manager.clear_recorded_data(
                    placement.x, placement.y, placement.p,
                    (IHCANMachineVertex.RECORDING_REGIONS.
                     SPIKE_PROBABILITY_REGION_ID.value))
        else:
            raise ConfigurationException(
                self.CLEAR_RECORDING_ERROR.format(variable))

    @overrides(AbstractNeuronRecordable.get_neuron_sampling_interval)
    def get_neuron_sampling_interval(self, variable, graph_mapper,
                                     local_time_period_map):
        if graph_mapper is None or local_time_period_map is None:
            return self.my_variable_local_time_period(
                get_simulator().default_machine_time_step, variable)

        if variable == DRNLMachineVertex.MOC:
            return self._drnl_neuron_recorder.get_neuron_sampling_interval(
                variable, self._drnl_vertices[0], local_time_period_map)
        elif variable in IHCANMachineVertex.RECORDABLES:
            return self._ihcan_neuron_recorder.get_neuron_sampling_interval(
                variable, self._ihcan_vertices[0], local_time_period_map)
        else:
            raise Exception(self.SAMPLING_INTERVAL_ERROR.format(variable))

    @overrides(AbstractNeuronRecordable.set_recording)
    def set_recording(self,
                      variable,
                      default_machine_time_step,
                      new_state=True,
                      sampling_interval=None,
                      indexes=None):
        self._change_requires_mapping = not self.is_recording(variable)
        if variable == DRNLMachineVertex.MOC:
            self._drnl_neuron_recorder.set_recording(
                variable, sampling_interval, indexes, self,
                default_machine_time_step, new_state)
        elif variable in IHCANMachineVertex.RECORDABLES:
            self._ihcan_neuron_recorder.set_recording(
                variable, sampling_interval, indexes, self,
                default_machine_time_step, new_state)
        else:
            raise ConfigurationException(self.RECORDING_ERROR.format(variable))

    @overrides(AbstractNeuronRecordable.is_recording)
    def is_recording(self, variable):
        if variable == DRNLMachineVertex.MOC:
            return self._drnl_neuron_recorder.is_recording(variable)
        elif variable in IHCANMachineVertex.RECORDABLES:
            return self._ihcan_neuron_recorder.is_recording(variable)
        else:
            raise ConfigurationException(self.RECORDING_ERROR.format(variable))

    @overrides(AbstractNeuronRecordable.get_recording_slice)
    def get_recording_slice(self, graph_mapper, vertex):
        return vertex.recorded_slice()

    @overrides(AbstractNeuronRecordable.get_data)
    def get_data(self, variable, run_time, placements, graph_mapper,
                 buffer_manager, local_time_period_map):
        if variable == DRNLMachineVertex.MOC:
            return self._drnl_neuron_recorder.get_matrix_data(
                self._label, buffer_manager,
                DRNLMachineVertex.MOC_RECORDABLE_REGION_ID, placements,
                graph_mapper, self, variable, run_time, local_time_period_map)
        elif variable == IHCANMachineVertex.SPIKE_PROB:
            matrix_data = self._ihcan_neuron_recorder.get_matrix_data(
                self._label, buffer_manager, IHCANMachineVertex.
                RECORDING_REGIONS.SPIKE_PROBABILITY_REGION_ID.value,
                placements, graph_mapper, self, variable, run_time,
                local_time_period_map)

            # convert to n fibers per time step.
            new_matrix_data = list()
            for element in matrix_data[0]:
                seq_elements = list()
                for seq_index in range(0, self._model.seq_size):
                    seq_elements.append(
                        element[0 + seq_index::self._model.seq_size])
                for time_step in seq_elements:
                    new_matrix_data.append(time_step)
            return new_matrix_data, matrix_data[1][0:10], matrix_data[2]
        elif variable == IHCANMachineVertex.SPIKES:
            return self._ihcan_neuron_recorder.get_spikes(
                self._label, buffer_manager, IHCANMachineVertex.
                RECORDING_REGIONS.SPIKE_RECORDING_REGION_ID.value, placements,
                graph_mapper, self, run_time)
        else:
            raise ConfigurationException(self.RECORDING_ERROR.format(variable))

    def get_sampling_interval(self, sample_size_window):
        return ((self._timer_period * sample_size_window) *
                MICRO_TO_SECOND_CONVERSION)