Skip to content

Commit

Permalink
Fix issues with Poisson direct sources and test
Browse files Browse the repository at this point in the history
  • Loading branch information
rowleya committed Oct 11, 2024
1 parent d908ef5 commit 404283b
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
from spynnaker.pyNN.models.utility_models.delays import DelayExtensionVertex
from spynnaker.pyNN.models.neuron.synaptic_matrices import SynapticMatrices
from spynnaker.pyNN.models.neuron.neuron_data import NeuronData
from spynnaker.pyNN.types import Delay_Types
from spynnaker.pyNN.models.neuron.population_synapses_machine_vertex_common \
import (
SDRAM_PARAMS_SIZE as SYNAPSES_SDRAM_PARAMS_SIZE, KEY_CONFIG_SIZE,
Expand Down Expand Up @@ -506,7 +507,9 @@ def __handle_poisson_sources(self, label: str) -> Dict[Slice, List[Tuple[
pre_vertex = cast(SpikeSourcePoissonVertex, edge.pre_vertex)
conn = proj._synapse_information.connector
dynamics = proj._synapse_information.synapse_dynamics
if self.is_direct_poisson_source(pre_vertex, conn, dynamics):
delay = proj._synapse_information.delays
if self.is_direct_poisson_source(
pre_vertex, conn, dynamics, delay):
# Create the direct Poisson vertices here; the splitter
# for the Poisson will create any others as needed
for vertex_slice in self._get_fixed_slices():
Expand Down Expand Up @@ -534,11 +537,13 @@ def handles_source_vertex(self, projection: Projection) -> bool:
pre_vertex = edge.pre_vertex
connector = projection._synapse_information.connector
dynamics = projection._synapse_information.synapse_dynamics
return self.is_direct_poisson_source(pre_vertex, connector, dynamics)
delay = projection._synapse_information.delays
return self.is_direct_poisson_source(
pre_vertex, connector, dynamics, delay)

def is_direct_poisson_source(
self, pre_vertex: ApplicationVertex, connector: AbstractConnector,
dynamics: AbstractSynapseDynamics) -> bool:
dynamics: AbstractSynapseDynamics, delay: Delay_Types) -> bool:
"""
Determine if a given Poisson source can be created by this splitter.
Expand All @@ -552,14 +557,17 @@ def is_direct_poisson_source(
The synapse dynamics in use in the Projection
:type dynamics:
~spynnaker.pyNN.models.neuron.synapse_dynamics.AbstractSynapseDynamics
:param delay:
The delay in use in the Projection
:rtype: bool
"""
return (isinstance(pre_vertex, SpikeSourcePoissonVertex) and
isinstance(pre_vertex.splitter, SplitterPoissonDelegate) and
len(pre_vertex.outgoing_projections) == 1 and
pre_vertex.n_atoms == self.governed_app_vertex.n_atoms and
isinstance(connector, OneToOneConnector) and
isinstance(dynamics, SynapseDynamicsStatic))
isinstance(dynamics, SynapseDynamicsStatic) and
delay == SpynnakerDataView().get_simulation_time_step_ms())

@overrides(AbstractSplitterCommon.get_in_coming_slices)
def get_in_coming_slices(self) -> Sequence[Slice]:
Expand Down
16 changes: 15 additions & 1 deletion spynnaker/pyNN/models/neuron/synaptic_matrices.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,8 @@ def generate_data(self) -> None:
# pylint: disable=protected-access
app_edge = proj._projection_edge
synapse_info = proj._synapse_information
if self.__is_sdram_poisson_source(app_edge):
continue
app_key_info = self.__app_key_and_mask(app_edge, synapse_info)
d_app_key_info = self.__delay_app_key_and_mask(
app_edge, synapse_info)
Expand Down Expand Up @@ -478,7 +480,7 @@ def __get_app_key_and_mask(

def __app_key_and_mask(
self, app_edge: ProjectionApplicationEdge,
s_info: SynapseInformation) -> AppKeyInfo:
s_info: SynapseInformation) -> Optional[AppKeyInfo]:
"""
Get a key and mask for an incoming application vertex as a whole.
Expand Down Expand Up @@ -520,6 +522,16 @@ def __delay_app_key_and_mask(
r_info, app_edge.n_delay_stages, app_edge.pre_vertex,
s_info.partition_id)

def __is_sdram_poisson_source(self, app_edge):
# Avoid circular import
# pylint: disable=import-outside-toplevel
from spynnaker.pyNN.extra_algorithms.splitter_components import (
SplitterPoissonDelegate)
if isinstance(app_edge.pre_vertex.splitter, SplitterPoissonDelegate):
if app_edge.pre_vertex.splitter.send_over_sdram:
return True
return False

def get_connections_from_machine(
self, placement: Placement, app_edge: ProjectionApplicationEdge,
synapse_info: SynapseInformation) -> Sequence[NDArray]:
Expand All @@ -536,6 +548,8 @@ def get_connections_from_machine(
:py:const:`~.NUMPY_CONNECTORS_DTYPE`
:rtype: list(~numpy.ndarray)
"""
if self.__is_sdram_poisson_source(app_edge):
return app_edge.pre_vertex.read_connections(synapse_info)
matrix = self.__matrices[app_edge, synapse_info]
return matrix.get_connections(placement)

Expand Down
2 changes: 1 addition & 1 deletion spynnaker/pyNN/models/neuron/synaptic_matrix_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def __init__(
self, synapse_info: SynapseInformation,
app_edge: ProjectionApplicationEdge, n_synapse_types: int,
synaptic_matrix_region: int, max_atoms_per_core: int,
all_syn_block_sz: int, app_key_info: AppKeyInfo,
all_syn_block_sz: int, app_key_info: Optional[AppKeyInfo],
delay_app_key_info: Optional[AppKeyInfo],
weight_scales: NDArray[floating]):
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@
ReceivesSynapticInputsOverSDRAM)
from spynnaker.pyNN.utilities.constants import (
LIVE_POISSON_CONTROL_PARTITION_ID)
from spynnaker.pyNN.models.neuron.synapse_dynamics.types import (
NUMPY_CONNECTORS_DTYPE, ConnectionsArray)

if TYPE_CHECKING:
from .spike_source_poisson_vertex import SpikeSourcePoissonVertex
Expand Down Expand Up @@ -606,6 +608,32 @@ def read_parameters_from_machine(self, placement: Placement):
offset += PARAMS_WORDS_PER_RATE * BYTES_PER_WORD
self._pop_vertex.rates.set_value_by_id(i, numpy.array(rates))

def read_connections(
self, synapse_info: SynapseInformation) -> ConnectionsArray:
size = self.vertex_slice.n_atoms * SDRAM_EDGE_PARAMS_BYTES_PER_WEIGHT
placement = SpynnakerDataView().get_placement_of_vertex(self)
addr = locate_memory_region_for_placement(
placement, self._PoissonSpikeSourceRegions.SDRAM_EDGE_PARAMS)
addr += SDRAM_EDGE_PARAMS_BASE_BYTES
data = SpynnakerDataView().read_memory(
placement.x, placement.y, addr, size)
weights_encoded = numpy.frombuffer(data, dtype=uint16).astype(float)
weight_scales = (
next(iter(cast(AbstractEdgePartition,
self.__sdram_partition).edges))
.post_vertex.weight_scales)
weights = weights_encoded / weight_scales[synapse_info.synapse_type]
connections = numpy.zeros(
self.vertex_slice.n_atoms, dtype=NUMPY_CONNECTORS_DTYPE)
connections["source"] = numpy.arange(
self.vertex_slice.lo_atom, self.vertex_slice.hi_atom + 1)
connections["target"] = numpy.arange(
self.vertex_slice.lo_atom, self.vertex_slice.hi_atom + 1)
connections["weight"] = weights
connections["delay"] = \
SpynnakerDataView().get_simulation_time_step_ms()
return connections

@overrides(SendsSynapticInputsOverSDRAM.sdram_requirement)
def sdram_requirement(self, sdram_machine_edge: SDRAMMachineEdge) -> int:
if isinstance(sdram_machine_edge.post_vertex,
Expand Down
12 changes: 12 additions & 0 deletions spynnaker/pyNN/models/spike_source/spike_source_poisson_vertex.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,16 @@
ParameterHolder, PopulationApplicationVertex)
from spynnaker.pyNN.models.common.types import Names
from spynnaker.pyNN.utilities.buffer_data_type import BufferDataType
from spynnaker.pyNN.models.neuron.synapse_dynamics.types import (
ConnectionsArray)
from .spike_source_poisson_machine_vertex import (
SpikeSourcePoissonMachineVertex, _flatten, get_rates_bytes,
get_sdram_edge_params_bytes, get_expander_rates_bytes, get_params_bytes)
if TYPE_CHECKING:
from spinn_utilities.ranged.abstract_sized import Selector
from .spike_source_poisson import SpikeSourcePoisson
from .spike_source_poisson_variable import SpikeSourcePoissonVariable
from spynnaker.pyNN.models.neural_projections import SynapseInformation
from spynnaker.pyNN.models.projection import Projection
from spynnaker.pyNN.models.common.types import Values

Expand Down Expand Up @@ -693,3 +696,12 @@ def data(self) -> RangeDictionary[
@property
def n_colour_bits(self) -> int:
return self.__n_colour_bits

def read_connections(
self, synapse_info: SynapseInformation) -> List[ConnectionsArray]:
""" Read Poisson connections from the machine
"""
connections = list()
for m_vertex in self.machine_vertices:
connections.append(m_vertex.read_connections(synapse_info))
return connections
4 changes: 4 additions & 0 deletions spynnaker/pyNN/utilities/bit_field_utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ def _unique_edges(projections: Iterable[Projection]) -> Iterable[
# pylint: disable=protected-access
edge = proj._projection_edge
synapse_info = proj._synapse_information
# If there are no outgoing vertices, we should discount this edge
if not edge.pre_vertex.splitter.get_out_going_vertices(
synapse_info.partition_id):
continue
if (edge, synapse_info.partition_id) not in seen_edges:
seen_edges.add((edge, synapse_info.partition_id))
yield edge, synapse_info.partition_id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,16 @@ def check_rate(
self.assertAlmostEqual(expected, float(count) / float(n_neurons),
delta=tolerance)

def make_delta_pop(self, n_neurons, ssp, weight):
def make_delta_pop(self, n_neurons, ssp, weight, delay=1.0):
pop_1 = sim.Population(
n_neurons, sim.IF_curr_delta(**PARAMS), label='pop_1',
splitter=SplitterAbstractPopulationVertexNeuronsSynapses(1))
pop_1.initialize(v=0)
pop_1.record("v")
sim.Projection(ssp, pop_1, sim.OneToOneConnector(),
sim.StaticSynapse(weight=weight, delay=1.0))
return pop_1
proj = sim.Projection(
ssp, pop_1, sim.OneToOneConnector(),
sim.StaticSynapse(weight=weight, delay=delay))
return pop_1, proj

def recording_poisson_spikes_rate_0(self):
sim.setup(timestep=1.0, min_delay=1.0)
Expand All @@ -92,17 +93,72 @@ def recording_poisson_spikes_rate_0(self):
ssp = sim.Population(
n_neurons, sim.SpikeSourcePoisson, {'rate': 0}, label='ssp')
ssp.record("spikes")
pop_1 = self.make_delta_pop(n_neurons, ssp, 1.0)
pop_1, proj = self.make_delta_pop(n_neurons, ssp, 1.0)

sim.run(2000)
v = pop_1.get_data("v").segments[0].filter(name='v')[0]
spikes = ssp.get_data("spikes").segments[0].spiketrains
conns = list(proj.get(["weight", "delay"], format="list"))
is_poisson_direct = (
proj._projection_edge.pre_vertex.splitter.send_over_sdram)
sim.end()
self.check_rate(n_neurons, v, 1.0, 0.0, spikes, 2000, 0, 1.0)
assert is_poisson_direct
self.check_rate(n_neurons, v, 1.0, 0.0, spikes, 2.0, 0, 1.0)
for i, j, w, d in conns:
assert i == j
assert w == 1.0
assert d == 1.0

def test_recording_poisson_spikes_rate_0(self):
self.runsafe(self.recording_poisson_spikes_rate_0)

def poisson_sdram_with_delay(self):
sim.setup(timestep=1.0, min_delay=1.0)
n_neurons = 100 # number of neurons in each population
sim.set_number_of_neurons_per_core(sim.IF_curr_delta, n_neurons / 2)

ssp = sim.Population(
n_neurons, sim.SpikeSourcePoisson, {'rate': 100}, label='ssp')
ssp.record("spikes")
_pop_1, proj = self.make_delta_pop(n_neurons, ssp, 1.0, delay=17)

sim.run(2000)
conns = list(proj.get(["weight", "delay"], format="list"))
is_poisson_direct = (
proj._projection_edge.pre_vertex.splitter.send_over_sdram)
sim.end()
assert not is_poisson_direct
# Can't really check the rate here as we expect it not use SDRAM!
for i, j, w, d in conns:
assert i == j
assert w == 1.0
assert d == 17.0

def poisson_sdram_with_delay_different_ts(self):
sim.setup(timestep=0.1, min_delay=1.0)
n_neurons = 100 # number of neurons in each population
sim.set_number_of_neurons_per_core(sim.IF_curr_delta, n_neurons / 2)

ssp = sim.Population(
n_neurons, sim.SpikeSourcePoisson, {'rate': 100}, label='ssp')
ssp.record("spikes")
_pop_1, proj = self.make_delta_pop(n_neurons, ssp, 1.0, delay=1.0)

sim.run(2000)
conns = list(proj.get(["weight", "delay"], format="list"))
is_poisson_direct = (
proj._projection_edge.pre_vertex.splitter.send_over_sdram)
sim.end()
assert not is_poisson_direct
# Can't really check the rate here as we expect it not use SDRAM!
for i, j, w, d in conns:
assert i == j
assert w == 1.0
assert d == 1.0

def test_poisson_sdram_with_delay(self):
self.runsafe(self.poisson_sdram_with_delay)

def check_rates(self, rates, seconds, seed):
n_neurons = 100
weight = 2.0
Expand All @@ -114,15 +170,16 @@ def check_rates(self, rates, seconds, seed):
label='inputSpikes_{}'.format(rate),
additional_parameters={"seed": seed})
ssp.record("spikes")
target = self.make_delta_pop(n_neurons, ssp, weight)
pops[rate] = (ssp, target)
target, proj = self.make_delta_pop(n_neurons, ssp, weight)
pops[rate] = (ssp, target, proj)
sim.run(seconds * 1000)
v = {}
spikes = {}
max_spikes = {}
max_weight = {}
is_direct = {}
for rate in rates:
ssp, target = pops[rate]
ssp, target, proj = pops[rate]
v[rate] = target.get_data("v").segments[0].filter(name='v')[0]
spikes[rate] = ssp.get_data("spikes").segments[0].spiketrains

Expand All @@ -132,8 +189,11 @@ def check_rates(self, rates, seconds, seed):
max_w = 65535 / weight_scale
max_weight[rate] = max_w
max_spikes[rate] = int(max_w / weight)
is_direct[rate] = (
proj._projection_edge.pre_vertex.splitter.send_over_sdram)
sim.end()
for rate in rates:
assert is_direct[rate]
self.check_rate(
n_neurons, v[rate], weight, rate, spikes[rate],
seconds, max_spikes[rate], max_weight[rate])
Expand Down

0 comments on commit 404283b

Please sign in to comment.