diff --git a/spynnaker/pyNN/__init__.py b/spynnaker/pyNN/__init__.py index 9cce523de97..d2d86d32e5c 100644 --- a/spynnaker/pyNN/__init__.py +++ b/spynnaker/pyNN/__init__.py @@ -25,21 +25,18 @@ import logging import os from typing import ( - Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple, Type, - TypedDict, Union, cast) + Any, Callable, Dict, Iterable, List, Optional, Tuple, Type, Union, cast) import numpy as __numpy from typing_extensions import Literal from numpy.typing import NDArray -from pyNN import common as pynn_common from pyNN.common import control as _pynn_control from pyNN.recording import get_io from pyNN.random import NumpyRNG from pyNN.space import ( Space, Line, Grid2D, Grid3D, Cuboid, Sphere, RandomStructure) from pyNN.space import distance as _pynn_distance -from neo import Block from spinn_utilities.exceptions import SimulatorNotSetupException from spinn_utilities.log import FormatAdapter @@ -56,7 +53,7 @@ import spynnaker.pyNN as _sim # pylint: disable=import-self from spynnaker.pyNN.exceptions import SpynnakerException - +from spynnaker.pyNN.models.common.types import Names from spynnaker.pyNN.random_distribution import RandomDistribution from spynnaker.pyNN.data import SpynnakerDataView from spynnaker.pyNN.models.abstract_pynn_model import AbstractPyNNModel @@ -205,30 +202,6 @@ 'get_max_delay', 'initialize', 'list_standard_models', 'name', 'record', "get_machine"] - -class __PynnOperations(TypedDict, total=False): - run: Callable[[float, Any], float] - run_until: Callable[[float, Any], float] - get_current_time: Callable[[], float] - get_time_step: Callable[[], float] - get_max_delay: Callable[[], int] - get_min_delay: Callable[[], int] - num_processes: Callable[[], int] - rank: Callable[[], int] - reset: Callable[[Dict[str, Any]], None] - create: Callable[ - [Union[Type, AbstractPyNNModel], Optional[Dict[str, Any]], int], - Population] - connect: Callable[ - [Population, Population, float, Optional[float], Optional[str], int, - Optional[NumpyRNG]], None] - record: Callable[ - [Union[str, Sequence[str]], PopulationBase, str, Optional[float], - Optional[Dict[str, Any]]], Block] - - -# Dynamically-extracted operations from PyNN -__pynn: __PynnOperations = {} # Cache of the simulator created by setup __simulator: Optional[SpiNNaker] = None @@ -312,9 +285,6 @@ def setup(timestep: Optional[Union[float, Literal["auto"]]] = None, logger.warning( "max_delay is not supported by sPyNNaker so will be ignored") - # setup PyNN common stuff - pynn_common.setup(timestep, min_delay, **extra_params) - # create stuff simulator if SpynnakerDataView.is_setup(): logger.warning("Calling setup a second time causes the previous " @@ -340,8 +310,6 @@ def setup(timestep: Optional[Union[float, Literal["auto"]]] = None, logger.warning("Extra params {} have been applied to the setup " "command which we do not consider", extra_params) - # get overloaded functions from PyNN in relation of our simulator object - _create_overloaded_functions(__simulator) SpynnakerDataView.add_database_socket_addresses(database_socket_addresses) return rank() @@ -385,32 +353,6 @@ def Projection( partition_id=partition_id) -def _create_overloaded_functions(spinnaker_simulator: SpiNNaker) -> None: - """ - Creates functions that the main PyNN interface supports - (given from PyNN) - - :param spinnaker_simulator: the simulator object we use underneath - """ - # overload the failed ones with now valid ones, now that we're in setup - # phase. - __pynn["run"], __pynn["run_until"] = pynn_common.build_run( - spinnaker_simulator) - - __pynn["get_current_time"], __pynn["get_time_step"], \ - __pynn["get_min_delay"], __pynn["get_max_delay"], \ - __pynn["num_processes"], __pynn["rank"] = \ - pynn_common.build_state_queries(spinnaker_simulator) - - __pynn["reset"] = pynn_common.build_reset(spinnaker_simulator) - __pynn["create"] = pynn_common.build_create(Population) - - __pynn["connect"] = pynn_common.build_connect( - Projection, FixedProbabilityConnector, StaticSynapse) - - __pynn["record"] = pynn_common.build_record(spinnaker_simulator) - - def end(_: Any = True) -> None: """ Cleans up the SpiNNaker machine and software @@ -520,7 +462,7 @@ def set_allow_delay_extensions( def connect(pre: Population, post: Population, weight: float = 0.0, - delay: Optional[float] = None, receptor_type: Optional[str] = None, + delay: Optional[float] = None, receptor_type: str = "excitatory", p: int = 1, rng: Optional[NumpyRNG] = None) -> None: """ Builds a projection. @@ -531,10 +473,20 @@ def connect(pre: Population, post: Population, weight: float = 0.0, :param delay: the delay of the connections :param receptor_type: excitatory / inhibitory :param p: probability - :param rng: random number generator + :param rng: random number generator (ignored) """ SpynnakerDataView.check_user_can_act() - __pynn["connect"](pre, post, weight, delay, receptor_type, p, rng) + if isinstance(pre, IDMixin): + pre = pre.as_view() + if isinstance(post, IDMixin): + post = post.as_view() + connector = FixedProbabilityConnector(p_connect=p) + synapse = StaticSynapse(weight=weight, delay=delay) + if rng is not None: + warn_once( + logger, "The rng argument to connect is ignored in sPyNNaker.") + Projection(pre, post, connector, receptor_type=receptor_type, + synapse_type=synapse) def create( @@ -550,7 +502,7 @@ def create( :returns: A new Population """ SpynnakerDataView.check_user_can_act() - return __pynn["create"](cellclass, cellparams, n) + return Population(n, cellclass, cellparams) def NativeRNG(seed_value: Union[int, List[int], NDArray]) -> None: @@ -569,10 +521,11 @@ def get_current_time() -> float: :return: returns the current time """ SpynnakerDataView.check_user_can_act() - return __pynn["get_current_time"]() + assert __simulator is not None + return __simulator.t -def get_min_delay() -> int: +def get_min_delay() -> float: """ The minimum allowed synaptic delay; delays will be clamped to be at least this. @@ -580,10 +533,11 @@ def get_min_delay() -> int: :return: returns the min delay of the simulation """ SpynnakerDataView.check_user_can_act() - return __pynn["get_min_delay"]() + assert __simulator is not None + return __simulator.dt -def get_max_delay() -> int: +def get_max_delay() -> float: """ Part of the PyNN API but does not make sense for sPyNNaker as different Projection, Vertex splitter combination could have different @@ -605,7 +559,8 @@ def get_time_step() -> float: :return: get the time step of the simulation (in ms) """ SpynnakerDataView.check_user_can_act() - return float(__pynn["get_time_step"]()) + assert __simulator is not None + return __simulator.dt def initialize(cells: PopulationBase, **initial_values: Any) -> None: @@ -616,7 +571,8 @@ def initialize(cells: PopulationBase, **initial_values: Any) -> None: :param initial_values: the parameters and their values to change """ SpynnakerDataView.check_user_can_act() - pynn_common.initialize(cells, **initial_values) + assert isinstance(cells, (Population, Assembly)), type(cells) + cells.initialize(**initial_values) def num_processes() -> int: @@ -628,8 +584,7 @@ def num_processes() -> int: :return: the number of MPI processes """ - SpynnakerDataView.check_user_can_act() - return __pynn["num_processes"]() + return 1 def rank() -> int: @@ -641,13 +596,12 @@ def rank() -> int: :return: MPI rank """ - SpynnakerDataView.check_user_can_act() - return __pynn["rank"]() + return 0 -def record(variables: Union[str, Sequence[str]], source: PopulationBase, +def record(variables: Names, source: PopulationBase, filename: str, sampling_interval: Optional[float] = None, - annotations: Optional[Dict[str, Any]] = None) -> Block: + annotations: Optional[Dict[str, Any]] = None) -> None: """ Sets variables to be recorded. @@ -659,11 +613,15 @@ def record(variables: Union[str, Sequence[str]], source: PopulationBase, :param sampling_interval: how often to sample the recording, not ignored so far :param annotations: the annotations to data writers - :return: neo object """ SpynnakerDataView.check_user_can_act() - return __pynn["record"](variables, source, filename, sampling_interval, - annotations) + if not isinstance(source, (Population, Assembly)): + if isinstance(source, (IDMixin)): + source = source.as_view() + source.record(variables, to_file=filename, + sampling_interval=sampling_interval) + if annotations: + source.annotate(**annotations) def reset(annotations: Optional[Dict[str, Any]] = None) -> None: @@ -674,11 +632,49 @@ def reset(annotations: Optional[Dict[str, Any]] = None) -> None: """ if annotations is None: annotations = {} - SpynnakerDataView.check_user_can_act() - __pynn["reset"](annotations) + assert __simulator is not None + __simulator.reset() + +def _run_until(time_point: float, + callbacks: Optional[List[Callable]] = None) -> float: + """ + Advance the simulation until a given time. -def run(simtime: float, callbacks: Optional[Callable] = None) -> float: + :param time_point: the time to run until (in ms) + :param callbacks: an optional list of callback functions, each of which + accepts the current time as an argument, and returns the next time it + wishes to be called. + :returns: the actual simulation time that the simulation stopped at + """ + assert __simulator is not None + now = __simulator.t + # allow for floating point error + if time_point - now < -__simulator.dt / 2.0: + raise ValueError( + f"Time {time_point} is in the past (current time {now})") + if callbacks: + callback_events = [(callback(__simulator.t), callback) + for callback in callbacks] + while __simulator.t + 1e-9 < time_point: + callback_events.sort(key=lambda cbe: cbe[0], reverse=True) + nxt, callback = callback_events.pop() + # collapse multiple events that happen within the same timestep + active_callbacks = [callback] + while (len(callback_events) > 0 and + abs(nxt - callback_events[-1][0]) < __simulator.dt): + active_callbacks.append(callback_events.pop()[1]) + + nxt = min(nxt, time_point) + __simulator.run_until(nxt) + callback_events.extend((callback(__simulator.t), callback) + for callback in active_callbacks) + else: + __simulator.run_until(time_point) + return __simulator.t + + +def run(simtime: float, callbacks: Optional[List[Callable]] = None) -> float: """ The run() function advances the simulation for a given number of milliseconds. @@ -688,7 +684,8 @@ def run(simtime: float, callbacks: Optional[Callable] = None) -> float: :return: the actual simulation time that the simulation stopped at """ SpynnakerDataView.check_user_can_act() - return __pynn["run"](simtime, callbacks) + assert __simulator is not None + return _run_until(__simulator.t + simtime, callbacks) # left here because needs to be done, and no better place to put it @@ -704,7 +701,7 @@ def run_until(tstop: float) -> float: :return: the actual simulation time that the simulation stopped at """ SpynnakerDataView.check_user_can_act() - return __pynn["run_until"](tstop, None) + return _run_until(tstop, None) def get_machine() -> Machine: diff --git a/spynnaker/pyNN/config_setup.py b/spynnaker/pyNN/config_setup.py index 3e1f1e39bbf..1ed004d5518 100644 --- a/spynnaker/pyNN/config_setup.py +++ b/spynnaker/pyNN/config_setup.py @@ -15,7 +15,7 @@ import os from typing import Set -from spinn_utilities.config_holder import clear_cfg_files +from spinn_utilities.config_holder import clear_cfg_files, set_config from spinn_front_end_common.interface.config_setup import ( add_default_cfg, add_spinnaker_cfg) from spinn_front_end_common.interface.config_setup import ( @@ -45,6 +45,7 @@ def unittest_setup() -> None: add_spynnaker_cfg() SpynnakerDataWriter.mock() AbstractPyNNNeuronModel.reset_all() + set_config("Reports", "write_pynn_report", "False") def add_spynnaker_cfg() -> None: diff --git a/spynnaker/pyNN/data/spynnaker_data_view.py b/spynnaker/pyNN/data/spynnaker_data_view.py index 261ab2ef8a4..1d2f0038f2a 100644 --- a/spynnaker/pyNN/data/spynnaker_data_view.py +++ b/spynnaker/pyNN/data/spynnaker_data_view.py @@ -13,9 +13,10 @@ # limitations under the License. from __future__ import annotations import logging -from typing import Iterator, Optional, Set, Tuple, TYPE_CHECKING +from typing import Iterator, Optional, Set, Tuple, TYPE_CHECKING, Any from spinn_utilities.log import FormatAdapter +from spinn_utilities.config_holder import get_config_bool, get_report_path from spinn_front_end_common.data import FecDataView @@ -51,7 +52,8 @@ class _SpynnakerDataModel(object): "_id_counter", "_min_delay", "_populations", - "_projections") + "_projections", + "_pynn_report") def __new__(cls) -> '_SpynnakerDataModel': if cls.__singleton is not None: @@ -70,6 +72,7 @@ def _clear(self) -> None: # Using a dict to verify if later could be stored here only self._populations: Set[Population] = set() self._projections: Set[Projection] = set() + self._pynn_report: Optional[str] = None def _hard_reset(self) -> None: """ @@ -233,3 +236,24 @@ def get_sim_name(cls) -> str: :returns: The name to be returned by `pyNN.spiNNaker.name`. """ return _version.NAME + + @classmethod + def write_pynn_report(cls, text: str, *args: Any, + **kwargs: Any) -> None: + """ + Writes text to the PyNN report file, or does nothing if the report is + disabled. + + :param text: The text to write to the report file. + :param args: Any additional arguments to format into the text using + `str.format`. + :param kwargs: Any additional keyword arguments to format into the text + using `str.format`. + """ + if not get_config_bool("Reports", "write_pynn_report"): + return + if cls.__spy_data._pynn_report is None: + cls.__spy_data._pynn_report = get_report_path("path_pynn_report") + with open(cls.__spy_data._pynn_report, "a", encoding="utf-8") as f: + f.write(text.format(*args, **kwargs)) + f.write("\n") diff --git a/spynnaker/pyNN/external_devices_models/threshold_type_multicast_device_control.py b/spynnaker/pyNN/external_devices_models/threshold_type_multicast_device_control.py index d0b0b52bba3..69bc8797697 100644 --- a/spynnaker/pyNN/external_devices_models/threshold_type_multicast_device_control.py +++ b/spynnaker/pyNN/external_devices_models/threshold_type_multicast_device_control.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Sequence +from typing import Sequence, Dict from spinn_utilities.overrides import overrides from spinn_utilities.ranged.range_dictionary import RangeDictionary @@ -23,6 +23,7 @@ from spynnaker.pyNN.utilities.struct import Struct from spynnaker.pyNN.external_devices_models import ( AbstractMulticastControllableDevice) +from spynnaker.pyNN.models.neuron.implementations import ModelParameter _DEVICE = "device" _KEY = "key" @@ -59,6 +60,21 @@ def __init__(self, devices: Sequence[AbstractMulticastControllableDevice]): _TYPE: ""}) self.__devices = devices + @overrides(AbstractThresholdType.get_param_values) + def get_param_values(self) -> Dict[str, ModelParameter]: + return { + _KEY: [d.device_control_key for d in self.__devices], + _SCALE: [d.device_control_scaling_factor for d in self.__devices], + _MIN: [d.device_control_min_value for d in self.__devices], + _MAX: [d.device_control_max_value for d in self.__devices], + _TS_INTER_SEND: [ + d.device_control_timesteps_between_sending + for d in self.__devices], + _TS_NEXT_SEND: [ + d.device_control_first_send_timestep for d in self.__devices], + _TYPE: [d.device_control_send_type.value + for d in self.__devices]} + @overrides(AbstractThresholdType.add_parameters) def add_parameters(self, parameters: RangeDictionary[float]) -> None: parameters[_KEY] = self._convert([ diff --git a/spynnaker/pyNN/models/neural_projections/connectors/multapse_connector.py b/spynnaker/pyNN/models/neural_projections/connectors/multapse_connector.py index 3b370547263..a87196fa2c8 100644 --- a/spynnaker/pyNN/models/neural_projections/connectors/multapse_connector.py +++ b/spynnaker/pyNN/models/neural_projections/connectors/multapse_connector.py @@ -276,7 +276,7 @@ def create_synaptic_block( return block def __repr__(self) -> str: - return f"MultapseConnector({self.__num_synapses})" + return f"FixedTotalNumberConnector({self.__num_synapses})" @property @overrides(AbstractGenerateConnectorOnMachine.gen_connector_id) diff --git a/spynnaker/pyNN/models/neuron/abstract_pynn_neuron_model_standard.py b/spynnaker/pyNN/models/neuron/abstract_pynn_neuron_model_standard.py index 0482cf7bbf4..dda573c9627 100644 --- a/spynnaker/pyNN/models/neuron/abstract_pynn_neuron_model_standard.py +++ b/spynnaker/pyNN/models/neuron/abstract_pynn_neuron_model_standard.py @@ -93,3 +93,6 @@ def create_vertex( splitter=splitter, seed=seed, n_colour_bits=n_colour_bits, neurons_per_core=neurons_per_core, n_synapse_cores=n_synapse_cores, allow_delay_extensions=allow_delay_extensions) + + def __str__(self) -> str: + return f"{self._model}" diff --git a/spynnaker/pyNN/models/neuron/additional_inputs/additional_input_ca2_adaptive.py b/spynnaker/pyNN/models/neuron/additional_inputs/additional_input_ca2_adaptive.py index cd83c5a8521..b8e5513311c 100644 --- a/spynnaker/pyNN/models/neuron/additional_inputs/additional_input_ca2_adaptive.py +++ b/spynnaker/pyNN/models/neuron/additional_inputs/additional_input_ca2_adaptive.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Dict + from spinn_utilities.overrides import overrides from spinn_utilities.ranged import RangeDictionary @@ -56,6 +58,13 @@ def __init__(self, tau_ca2: ModelParameter, i_ca2: ModelParameter, self.__i_ca2 = i_ca2 self.__i_alpha = i_alpha + @overrides(AbstractAdditionalInput.get_param_values) + def get_param_values(self) -> Dict[str, ModelParameter]: + return { + TAU_CA2: self.__tau_ca2, + I_ALPHA: self.__i_alpha, + TIME_STEP: SpynnakerDataView.get_simulation_time_step_ms()} + @overrides(AbstractAdditionalInput.add_parameters) def add_parameters(self, parameters: RangeDictionary[float]) -> None: parameters[TAU_CA2] = self._convert(self.__tau_ca2) diff --git a/spynnaker/pyNN/models/neuron/implementations/abstract_standard_neuron_component.py b/spynnaker/pyNN/models/neuron/implementations/abstract_standard_neuron_component.py index 445c4395ce9..34f6a18e591 100644 --- a/spynnaker/pyNN/models/neuron/implementations/abstract_standard_neuron_component.py +++ b/spynnaker/pyNN/models/neuron/implementations/abstract_standard_neuron_component.py @@ -109,3 +109,12 @@ def _convert(value: ModelParameter) -> \ return value # TODO: Is this correct? Without this, things will only handle floats return SpynnakerRangedList(None, value) + + @abstractmethod + def get_param_values(self) -> Dict[str, ModelParameter]: + """ + The parameters of the component and their values + + :returns: A dictionary mapping parameter names to their values + """ + raise NotImplementedError diff --git a/spynnaker/pyNN/models/neuron/implementations/neuron_impl_standard.py b/spynnaker/pyNN/models/neuron/implementations/neuron_impl_standard.py index 7af2f11eca6..b893f43b58f 100644 --- a/spynnaker/pyNN/models/neuron/implementations/neuron_impl_standard.py +++ b/spynnaker/pyNN/models/neuron/implementations/neuron_impl_standard.py @@ -197,3 +197,10 @@ def __getitem__(self, key: str) -> Any: # ... or fail raise AttributeError( f"'{self.__class__.__name__}' object has no attribute {key}") + + def __str__(self) -> str: + param_values: str = "" + for component in self.__components: + for param, value in component.get_param_values().items(): + param_values += f"{param}={value}, " + return f"{self.__class__.__name__}({param_values[:-2]})" diff --git a/spynnaker/pyNN/models/neuron/implementations/neuron_impl_stoc_exp.py b/spynnaker/pyNN/models/neuron/implementations/neuron_impl_stoc_exp.py index 5df9a7d92c5..df1eebc4d83 100644 --- a/spynnaker/pyNN/models/neuron/implementations/neuron_impl_stoc_exp.py +++ b/spynnaker/pyNN/models/neuron/implementations/neuron_impl_stoc_exp.py @@ -163,3 +163,9 @@ def get_units(self, variable: str) -> str: @overrides(AbstractNeuronImpl.is_conductance_based) def is_conductance_based(self) -> bool: return False + + def __str__(self) -> str: + return ( + f"NeuronImplStocExp(tau={self._tau}, bias={self._bias}, " + f"refract_init={self._refract_init}, " + f"seed={self._random.rng.seed})") diff --git a/spynnaker/pyNN/models/neuron/implementations/neuron_impl_stoc_exp_stable.py b/spynnaker/pyNN/models/neuron/implementations/neuron_impl_stoc_exp_stable.py index 1e788b4a893..9c62f9bc6c6 100644 --- a/spynnaker/pyNN/models/neuron/implementations/neuron_impl_stoc_exp_stable.py +++ b/spynnaker/pyNN/models/neuron/implementations/neuron_impl_stoc_exp_stable.py @@ -180,3 +180,11 @@ def get_units(self, variable: str) -> str: @overrides(AbstractNeuronImpl.is_conductance_based) def is_conductance_based(self) -> bool: return False + + def __str__(self) -> str: + return ( + f"{self.model_name}(v_init={self._v_init}, " + f"v_reset={self._v_reset}, tau={self._tau}, " + f"tau_refrac={self._tau_refrac}, bias={self._bias}, " + f"refract_init={self._refract_init}, " + f"seed={self._random.rng.seed})") diff --git a/spynnaker/pyNN/models/neuron/implementations/neuron_impl_stoc_sigma.py b/spynnaker/pyNN/models/neuron/implementations/neuron_impl_stoc_sigma.py index 19951409285..67ca66a36e7 100644 --- a/spynnaker/pyNN/models/neuron/implementations/neuron_impl_stoc_sigma.py +++ b/spynnaker/pyNN/models/neuron/implementations/neuron_impl_stoc_sigma.py @@ -166,3 +166,9 @@ def get_units(self, variable: str) -> str: @overrides(AbstractNeuronImpl.is_conductance_based) def is_conductance_based(self) -> bool: return False + + def __str__(self) -> str: + return (f"NeuronImplStocSigma(tau_refrac={self._tau_refrac}, " + f"alpha={self._alpha}, bias={self._bias}, " + f"refract_init={self._refract_init}, " + f"seed={self._random.rng.seed})") diff --git a/spynnaker/pyNN/models/neuron/input_types/input_type_conductance.py b/spynnaker/pyNN/models/neuron/input_types/input_type_conductance.py index 705f76efffc..92e172cfb47 100644 --- a/spynnaker/pyNN/models/neuron/input_types/input_type_conductance.py +++ b/spynnaker/pyNN/models/neuron/input_types/input_type_conductance.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Dict + from spinn_utilities.overrides import overrides from spinn_utilities.ranged.range_dictionary import RangeDictionary @@ -49,6 +51,10 @@ def __init__(self, e_rev_E: ModelParameter, e_rev_I: ModelParameter): self.__e_rev_E = e_rev_E self.__e_rev_I = e_rev_I + @overrides(AbstractInputType.get_param_values) + def get_param_values(self) -> Dict[str, ModelParameter]: + return {E_REV_E: self.__e_rev_E, E_REV_I: self.__e_rev_I} + @overrides(AbstractInputType.add_parameters) def add_parameters(self, parameters: RangeDictionary[float]) -> None: parameters[E_REV_E] = self._convert(self.__e_rev_E) diff --git a/spynnaker/pyNN/models/neuron/input_types/input_type_current.py b/spynnaker/pyNN/models/neuron/input_types/input_type_current.py index 3708f2dd682..ed93775d8d9 100644 --- a/spynnaker/pyNN/models/neuron/input_types/input_type_current.py +++ b/spynnaker/pyNN/models/neuron/input_types/input_type_current.py @@ -12,9 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Dict from spinn_utilities.overrides import overrides from spinn_utilities.ranged import RangeDictionary from spynnaker.pyNN.utilities.struct import Struct +from spynnaker.pyNN.models.neuron.implementations import ModelParameter from .abstract_input_type import AbstractInputType @@ -27,6 +29,10 @@ class InputTypeCurrent(AbstractInputType): def __init__(self) -> None: super().__init__([Struct([])], dict()) + @overrides(AbstractInputType.get_param_values) + def get_param_values(self) -> Dict[str, ModelParameter]: + return dict() + @overrides(AbstractInputType.add_parameters) def add_parameters(self, parameters: RangeDictionary[float]) -> None: pass diff --git a/spynnaker/pyNN/models/neuron/input_types/input_type_delta.py b/spynnaker/pyNN/models/neuron/input_types/input_type_delta.py index 61dd8356eba..e34d2ce3339 100644 --- a/spynnaker/pyNN/models/neuron/input_types/input_type_delta.py +++ b/spynnaker/pyNN/models/neuron/input_types/input_type_delta.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Dict + from spinn_utilities.overrides import overrides from spinn_utilities.ranged import RangeDictionary @@ -19,6 +21,7 @@ from spynnaker.pyNN.utilities.struct import Struct from spynnaker.pyNN.data import SpynnakerDataView +from spynnaker.pyNN.models.neuron.implementations import ModelParameter from .abstract_input_type import AbstractInputType @@ -37,6 +40,10 @@ def __init__(self) -> None: [Struct([(DataType.S1615, TIME_STEP)])], dict()) + @overrides(AbstractInputType.get_param_values) + def get_param_values(self) -> Dict[str, ModelParameter]: + return dict() + @overrides(AbstractInputType.add_parameters) def add_parameters(self, parameters: RangeDictionary[float]) -> None: parameters[TIME_STEP] = SpynnakerDataView.get_simulation_time_step_ms() diff --git a/spynnaker/pyNN/models/neuron/neuron_models/neuron_model_if_trunc.py b/spynnaker/pyNN/models/neuron/neuron_models/neuron_model_if_trunc.py index e73ce3f6c64..8f40e7c5cc5 100644 --- a/spynnaker/pyNN/models/neuron/neuron_models/neuron_model_if_trunc.py +++ b/spynnaker/pyNN/models/neuron/neuron_models/neuron_model_if_trunc.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Optional +from typing import Optional, Dict from spinn_utilities.overrides import overrides from spinn_utilities.ranged import RangeDictionary from spinn_front_end_common.interface.ds import DataType @@ -80,6 +80,16 @@ def __init__( self.__v_reset = v_reset self.__tau_refrac = tau_refrac + @overrides(AbstractStandardNeuronComponent.get_param_values) + def get_param_values(self) -> Dict[str, ModelParameter]: + return { + "v_init": self.__v_init, + "tau_m": self.__tau_m, + "cm": self.__cm, + "i_offset": self.__i_offset, + "v_reset": self.__v_reset, + "tau_refrac": self.__tau_refrac} + @overrides(AbstractStandardNeuronComponent.add_parameters) def add_parameters(self, parameters: RangeDictionary[float]) -> None: parameters[TAU_M] = self._convert(self.__tau_m) diff --git a/spynnaker/pyNN/models/neuron/neuron_models/neuron_model_izh.py b/spynnaker/pyNN/models/neuron/neuron_models/neuron_model_izh.py index cb4329a7093..c14c5e4c489 100644 --- a/spynnaker/pyNN/models/neuron/neuron_models/neuron_model_izh.py +++ b/spynnaker/pyNN/models/neuron/neuron_models/neuron_model_izh.py @@ -11,6 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +from typing import Dict from spinn_utilities.overrides import overrides from spinn_utilities.ranged import RangeDictionary from spinn_front_end_common.interface.ds import DataType @@ -73,6 +74,13 @@ def __init__( self.__v_init = v_init self.__u_init = u_init + @overrides(AbstractStandardNeuronComponent.get_param_values) + def get_param_values(self) -> Dict[str, ModelParameter]: + return { + A: self.__a, B: self.__b, C: self.__c, D: self.__d, + I_OFFSET: self.__i_offset, "v_init": self.__v_init, + "u_init": self.__u_init} + @overrides(AbstractStandardNeuronComponent.add_parameters) def add_parameters(self, parameters: RangeDictionary[float]) -> None: parameters[A] = self._convert(self.__a) diff --git a/spynnaker/pyNN/models/neuron/neuron_models/neuron_model_leaky_integrate_and_fire.py b/spynnaker/pyNN/models/neuron/neuron_models/neuron_model_leaky_integrate_and_fire.py index 1013b3de54d..f30ba790981 100644 --- a/spynnaker/pyNN/models/neuron/neuron_models/neuron_model_leaky_integrate_and_fire.py +++ b/spynnaker/pyNN/models/neuron/neuron_models/neuron_model_leaky_integrate_and_fire.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Optional +from typing import Optional, Dict from spinn_utilities.overrides import overrides from spinn_utilities.ranged import RangeDictionary from spinn_front_end_common.interface.ds import DataType @@ -84,6 +84,17 @@ def __init__( self.__v_reset = v_reset self.__tau_refrac = tau_refrac + @overrides(AbstractStandardNeuronComponent.get_param_values) + def get_param_values(self) -> Dict[str, ModelParameter]: + return { + "v_init": self.__v_init, + V_REST: self.__v_rest, + TAU_M: self.__tau_m, + CM: self.__cm, + I_OFFSET: self.__i_offset, + V_RESET: self.__v_reset, + TAU_REFRAC: self.__tau_refrac} + @overrides(AbstractStandardNeuronComponent.add_parameters) def add_parameters(self, parameters: RangeDictionary[float]) -> None: parameters[V_REST] = self._convert(self.__v_rest) diff --git a/spynnaker/pyNN/models/neuron/synapse_dynamics/abstract_synapse_dynamics.py b/spynnaker/pyNN/models/neuron/synapse_dynamics/abstract_synapse_dynamics.py index 09537b2a318..3087c28991d 100644 --- a/spynnaker/pyNN/models/neuron/synapse_dynamics/abstract_synapse_dynamics.py +++ b/spynnaker/pyNN/models/neuron/synapse_dynamics/abstract_synapse_dynamics.py @@ -60,12 +60,20 @@ def __init__(self, delay: WeightsDelysIn, :param weight: The weights or way to generate the weights """ self.__check_in_type(delay, "delay") - self.__delay = self._round_delay(delay) - self.__check_out_delay(self.__delay, "delay") self.__check_in_type(weight, "weight") + + self.__delay, delay_rounded = self._round_delay(delay) self.__weight = self._convert_weight(weight) + + self.__check_out_delay(self.__delay, "delay") self.__check_out_weight(self.__weight, "weight") + # This check is done here to ensure the class has all values + # initialized before a warning is displayed. + if delay_rounded: + logger.warning("Rounding up delay in {} from {} to {}", + self, delay, self.__delay) + def __check_in_type(self, value: WeightsDelysIn, name: str) -> None: if value is None: return @@ -155,17 +163,17 @@ def weight(self) -> Weights: """ return self.__weight - def _round_delay(self, delay: WeightsDelysIn) -> Delays: + def _round_delay(self, delay: WeightsDelysIn) -> Tuple[Delays, bool]: """ Round the delays to multiples of full timesteps. (otherwise SDRAM estimation calculations can go wrong) :param delay: - :return: Rounded delay + :return: Rounded delay, and whether to display a warning """ if isinstance(delay, (RandomDistribution, str)): - return delay + return delay, False if delay is None: delay = SpynnakerDataView.get_min_delay() # Note the cast is just to say trust use the delay will work @@ -174,13 +182,11 @@ def _round_delay(self, delay: WeightsDelysIn) -> Delays: numpy.rint(numpy.array(cast(float, delay)) * SpynnakerDataView.get_simulation_time_step_per_ms()) * SpynnakerDataView.get_simulation_time_step_ms()) - if not numpy.allclose(cast(float, delay), new_delay): - logger.warning("Rounding up delay in f{} from {} to {}", - self, delay, new_delay) + delay_rounded = not numpy.allclose(cast(float, delay), new_delay) if isinstance(new_delay, numpy.float64): - return float(new_delay) + return float(new_delay), delay_rounded if isinstance(new_delay, numpy.ndarray): - return new_delay + return new_delay, delay_rounded raise TypeError(f"{type(delay)=}") def _convert_weight(self, weight: WeightsDelysIn) -> Weights: diff --git a/spynnaker/pyNN/models/neuron/synapse_dynamics/synapse_dynamics_static.py b/spynnaker/pyNN/models/neuron/synapse_dynamics/synapse_dynamics_static.py index 2ab58ecce48..3c02ed6f51b 100644 --- a/spynnaker/pyNN/models/neuron/synapse_dynamics/synapse_dynamics_static.py +++ b/spynnaker/pyNN/models/neuron/synapse_dynamics/synapse_dynamics_static.py @@ -252,3 +252,7 @@ def synapses_per_second(self) -> int: # From Synapse-Centric Mapping of Cortical Models to the SpiNNaker # Neuromorphic Architecture return 13000000 + + def __str__(self) -> str: + return ( + f"SynapseDynamicsStatic(weight={self.weight}, delay={self.delay})") diff --git a/spynnaker/pyNN/models/neuron/synapse_types/synapse_type_alpha.py b/spynnaker/pyNN/models/neuron/synapse_types/synapse_type_alpha.py index d964b1e485e..07d79de8f16 100644 --- a/spynnaker/pyNN/models/neuron/synapse_types/synapse_type_alpha.py +++ b/spynnaker/pyNN/models/neuron/synapse_types/synapse_type_alpha.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Optional, Tuple +from typing import Optional, Tuple, Dict from spinn_utilities.overrides import overrides from spinn_utilities.ranged import RangeDictionary @@ -85,6 +85,16 @@ def __init__( self.__inh_exp_response = inh_exp_response self.__tau_syn_I = tau_syn_I + @overrides(AbstractSynapseType.get_param_values) + def get_param_values(self) -> Dict[str, ModelParameter]: + return { + EXC_RESPONSE: self.__exc_response, + EXC_EXP_RESPONSE: self.__exc_exp_response, + TAU_SYN_E: self.__tau_syn_E, + INH_RESPONSE: self.__inh_response, + INH_EXP_RESPONSE: self.__inh_exp_response, + TAU_SYN_I: self.__tau_syn_I} + @overrides(AbstractSynapseType.add_parameters) def add_parameters(self, parameters: RangeDictionary[float]) -> None: parameters[TAU_SYN_E] = self._convert(self.__tau_syn_E) diff --git a/spynnaker/pyNN/models/neuron/synapse_types/synapse_type_delta.py b/spynnaker/pyNN/models/neuron/synapse_types/synapse_type_delta.py index 2e9ba68c70d..11e6aae9607 100644 --- a/spynnaker/pyNN/models/neuron/synapse_types/synapse_type_delta.py +++ b/spynnaker/pyNN/models/neuron/synapse_types/synapse_type_delta.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Optional, Tuple +from typing import Optional, Tuple, Dict from spinn_utilities.overrides import overrides from spinn_utilities.ranged import RangeDictionary @@ -48,6 +48,13 @@ def __init__(self, isyn_exc: ModelParameter, isyn_inh: ModelParameter): self.__isyn_exc = isyn_exc self.__isyn_inh = isyn_inh + @overrides(AbstractSynapseType.get_param_values) + def get_param_values(self) -> Dict[str, ModelParameter]: + return { + ISYN_EXC: self.__isyn_exc, + ISYN_INH: self.__isyn_inh + } + @overrides(AbstractSynapseType.add_parameters) def add_parameters(self, parameters: RangeDictionary[float]) -> None: pass diff --git a/spynnaker/pyNN/models/neuron/synapse_types/synapse_type_dual_exponential.py b/spynnaker/pyNN/models/neuron/synapse_types/synapse_type_dual_exponential.py index e8fcc680463..e61b65d7608 100644 --- a/spynnaker/pyNN/models/neuron/synapse_types/synapse_type_dual_exponential.py +++ b/spynnaker/pyNN/models/neuron/synapse_types/synapse_type_dual_exponential.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Optional, Tuple +from typing import Optional, Tuple, Dict from spinn_utilities.overrides import overrides from spinn_utilities.ranged import RangeDictionary @@ -77,6 +77,16 @@ def __init__( self.__isyn_exc2 = isyn_exc2 self.__isyn_inh = isyn_inh + @overrides(AbstractSynapseType.get_param_values) + def get_param_values(self) -> Dict[str, ModelParameter]: + return { + TAU_SYN_E: self.__tau_syn_E, + TAU_SYN_E2: self.__tau_syn_E2, + TAU_SYN_I: self.__tau_syn_I, + ISYN_EXC: self.__isyn_exc, + ISYN_EXC2: self.__isyn_exc2, + ISYN_INH: self.__isyn_inh} + @overrides(AbstractSynapseType.add_parameters) def add_parameters(self, parameters: RangeDictionary[float]) -> None: parameters[TAU_SYN_E] = self._convert(self.__tau_syn_E) diff --git a/spynnaker/pyNN/models/neuron/synapse_types/synapse_type_exponential.py b/spynnaker/pyNN/models/neuron/synapse_types/synapse_type_exponential.py index 926e8389b83..1e5a160b010 100644 --- a/spynnaker/pyNN/models/neuron/synapse_types/synapse_type_exponential.py +++ b/spynnaker/pyNN/models/neuron/synapse_types/synapse_type_exponential.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Optional, Tuple +from typing import Optional, Tuple, Dict from spinn_utilities.overrides import overrides from spinn_utilities.ranged import RangeDictionary @@ -64,6 +64,14 @@ def __init__(self, tau_syn_E: ModelParameter, tau_syn_I: ModelParameter, self.__isyn_exc = isyn_exc self.__isyn_inh = isyn_inh + @overrides(AbstractSynapseType.get_param_values) + def get_param_values(self) -> Dict[str, ModelParameter]: + return { + TAU_SYN_E: self.__tau_syn_E, + TAU_SYN_I: self.__tau_syn_I, + ISYN_EXC: self.__isyn_exc, + ISYN_INH: self.__isyn_inh} + @overrides(AbstractSynapseType.add_parameters) def add_parameters(self, parameters: RangeDictionary[float]) -> None: parameters[TAU_SYN_E] = self._convert(self.__tau_syn_E) diff --git a/spynnaker/pyNN/models/neuron/synapse_types/synapse_type_semd.py b/spynnaker/pyNN/models/neuron/synapse_types/synapse_type_semd.py index 9678d9a674e..6ab19d9a7f6 100644 --- a/spynnaker/pyNN/models/neuron/synapse_types/synapse_type_semd.py +++ b/spynnaker/pyNN/models/neuron/synapse_types/synapse_type_semd.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Optional, Tuple +from typing import Optional, Tuple, Dict from spinn_utilities.overrides import overrides from spinn_utilities.ranged import RangeDictionary @@ -97,6 +97,19 @@ def __init__( self.__exc2_old = exc2_old self.__scaling_factor = scaling_factor + @overrides(AbstractSynapseType.get_param_values) + def get_param_values(self) -> Dict[str, ModelParameter]: + return { + TAU_SYN_E: self.__tau_syn_E, + TAU_SYN_E2: self.__tau_syn_E2, + TAU_SYN_I: self.__tau_syn_I, + ISYN_EXC: self.__isyn_exc, + ISYN_EXC2: self.__isyn_exc2, + ISYN_INH: self.__isyn_inh, + MULTIPLICATOR: self.__multiplicator, + EXC2_OLD: self.__exc2_old, + SCALING_FACTOR: self.__scaling_factor} + @overrides(AbstractSynapseType.add_parameters) def add_parameters(self, parameters: RangeDictionary[float]) -> None: parameters[TAU_SYN_E] = self._convert(self.__tau_syn_E) diff --git a/spynnaker/pyNN/models/neuron/threshold_types/threshold_type_fixed_prob.py b/spynnaker/pyNN/models/neuron/threshold_types/threshold_type_fixed_prob.py index dae0d414a8f..61894cbe59e 100644 --- a/spynnaker/pyNN/models/neuron/threshold_types/threshold_type_fixed_prob.py +++ b/spynnaker/pyNN/models/neuron/threshold_types/threshold_type_fixed_prob.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Optional +from typing import Optional, Dict from pyNN.random import NumpyRNG @@ -62,6 +62,10 @@ def __init__(self, v_thresh: ModelParameter, p_thresh: ModelParameter, self.__random = RandomDistribution( "uniform", low=0, high=0xFFFFFFFF, rng=NumpyRNG(seed)) + @overrides(AbstractThresholdType.get_param_values) + def get_param_values(self) -> Dict[str, ModelParameter]: + return {V_THRESH: self.__v_thresh, P_THRESH: self.__p_thresh} + @overrides(AbstractThresholdType.add_parameters) def add_parameters(self, parameters: RangeDictionary[float]) -> None: parameters[V_THRESH] = self._convert(self.__v_thresh) diff --git a/spynnaker/pyNN/models/neuron/threshold_types/threshold_type_maass_stochastic.py b/spynnaker/pyNN/models/neuron/threshold_types/threshold_type_maass_stochastic.py index fa6f8043f0a..694fbcb226a 100644 --- a/spynnaker/pyNN/models/neuron/threshold_types/threshold_type_maass_stochastic.py +++ b/spynnaker/pyNN/models/neuron/threshold_types/threshold_type_maass_stochastic.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Dict + from spinn_utilities.overrides import overrides from spinn_utilities.ranged import RangeDictionary @@ -61,6 +63,13 @@ def __init__(self, du_th: ModelParameter, tau_th: ModelParameter, self.__tau_th = tau_th self.__v_thresh = v_thresh + @overrides(AbstractThresholdType.get_param_values) + def get_param_values(self) -> Dict[str, ModelParameter]: + return { + DU_TH: self.__du_th, + TAU_TH: self.__tau_th, + V_THRESH: self.__v_thresh} + @overrides(AbstractThresholdType.add_parameters) def add_parameters(self, parameters: RangeDictionary[float]) -> None: parameters[DU_TH] = self._convert(self.__du_th) diff --git a/spynnaker/pyNN/models/neuron/threshold_types/threshold_type_static.py b/spynnaker/pyNN/models/neuron/threshold_types/threshold_type_static.py index 45056799741..f979bae1ac4 100644 --- a/spynnaker/pyNN/models/neuron/threshold_types/threshold_type_static.py +++ b/spynnaker/pyNN/models/neuron/threshold_types/threshold_type_static.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Dict + from spinn_utilities.overrides import overrides from spinn_utilities.ranged import RangeDictionary @@ -40,6 +42,10 @@ def __init__(self, v_thresh: ModelParameter): {V_THRESH: "mV"}) self.__v_thresh = v_thresh + @overrides(AbstractThresholdType.get_param_values) + def get_param_values(self) -> Dict[str, ModelParameter]: + return {V_THRESH: self.__v_thresh} + @overrides(AbstractThresholdType.add_parameters) def add_parameters(self, parameters: RangeDictionary[float]) -> None: parameters[V_THRESH] = self._convert(self.__v_thresh) diff --git a/spynnaker/pyNN/models/populations/population.py b/spynnaker/pyNN/models/populations/population.py index 31bc036c540..56e262a00fd 100644 --- a/spynnaker/pyNN/models/populations/population.py +++ b/spynnaker/pyNN/models/populations/population.py @@ -68,7 +68,6 @@ class Population(PopulationBase): """ # pylint: disable=redefined-builtin __slots__ = ( - "__annotations", "__celltype", "__first_id", "__last_id", @@ -100,6 +99,8 @@ def __init__( :param additional_kwargs: A nicer way of allowing additional things """ + super().__init__() + # Deal with the kwargs! additional: _ParamDict = dict() if additional_parameters is not None: @@ -129,7 +130,6 @@ def __init__( if realsize is None: realsize = self.__vertex.n_atoms self.__size = realsize - self.__annotations: Dict[str, Any] = dict() # things for pynn demands self.__first_id, self.__last_id = SpynnakerDataView.add_population( @@ -140,6 +140,10 @@ def __init__( for variable, value in initial_values.items(): self.__vertex.set_initial_state_values(variable, value) + SpynnakerDataView.write_pynn_report( + "{} = Population({}, {}, structure={}, initial_values={})", + label, size, model, structure, initial_values) + def __iter__(self) -> Iterator[PopulationView]: """ Iterate over local cells. @@ -160,13 +164,6 @@ def all(self) -> Iterator[PopulationView]: for _id in range(self.__size): yield IDMixin(self, _id) - @property - def annotations(self) -> Dict[str, Any]: - """ - The annotations given by the end user. - """ - return self.__annotations - @property def celltype(self) -> AbstractPyNNModel: """ @@ -188,6 +185,9 @@ def record(self, variables: Names, to_file: IoDest = None, sampling_interval: Optional[float] = None) -> None: self.__recorder.record( variables, to_file, sampling_interval, indexes=None) + SpynnakerDataView.write_pynn_report( + "{}.record(variables={}, to_file={}, sampling_interval={})", + self.label, variables, to_file, sampling_interval) def sample(self, n: int, rng: Optional[NumpyRNG] = None) -> PopulationView: """ @@ -370,6 +370,8 @@ def initialize(self, **kwargs: Values) -> None: " consider calling the non-PyNN function set_state instead.") for variable, value in kwargs.items(): self.__vertex.set_initial_state_values(variable, value) + SpynnakerDataView.write_pynn_report( + "{}.initialize({})", self.label, kwargs) @property def initial_values(self) -> ParameterHolder: @@ -590,6 +592,8 @@ def inject(self, current_source: NeuronCurrentSource) -> None: """ # Pass this into the vertex self.__vertex.inject(current_source, [n for n in range(self.__size)]) + SpynnakerDataView.write_pynn_report( + "{}.inject({})", self.label, current_source) def __len__(self) -> int: """ diff --git a/spynnaker/pyNN/models/populations/population_base.py b/spynnaker/pyNN/models/populations/population_base.py index fe9a1fd84fe..19fa6d67771 100644 --- a/spynnaker/pyNN/models/populations/population_base.py +++ b/spynnaker/pyNN/models/populations/population_base.py @@ -47,7 +47,28 @@ class PopulationBase(object, metaclass=AbstractBase): Mainly pass through and not implemented. """ - __slots__ = () + __slots__ = ( + "__annotations",) + + def __init__(self) -> None: + self.__annotations: Dict[str, Any] = dict() + + def annotate(self, **annotations: Any) -> None: + """ + Add annotations to this population. These are user-defined key-value + pairs that will be stored with the population and can be retrieved + later with the `annotations` property. + + :param annotations: The annotations to add to the population. + """ + self.__annotations.update(annotations) + + @property + def annotations(self) -> Dict[str, Any]: + """ + The annotations given by the end user. + """ + return self.__annotations @property def local_cells(self) -> Sequence[IDMixin]: diff --git a/spynnaker/pyNN/models/populations/population_view.py b/spynnaker/pyNN/models/populations/population_view.py index cda8cfbe157..fa15ff66fcf 100644 --- a/spynnaker/pyNN/models/populations/population_view.py +++ b/spynnaker/pyNN/models/populations/population_view.py @@ -68,7 +68,6 @@ class PopulationView(PopulationBase): Selector to Id is actually handled by :py:class:`~.AbstractSized`. """ __slots__ = ( - "__annotations", "__indexes", "__label", "__mask", @@ -77,7 +76,9 @@ class PopulationView(PopulationBase): "__vertex", "__recorder") - __realslots__ = frozenset("_PopulationView" + item for item in __slots__) + __realslots__ = frozenset( + ["_PopulationView" + item for item in __slots__] + + ["_PopulationBase__annotations"]) def __init__( self, parent: Union[Population, 'PopulationView'], @@ -99,6 +100,7 @@ def __init__( will all create the same view. :param label: A label for the view """ + super().__init__() self.__parent = parent sized = AbstractSized(parent.size) ids = sized.selector_to_ids(selector, warn=True) @@ -113,7 +115,6 @@ def __init__( if label is None: label = f"{parent.label}:{selector}" self.__label = label - self.__annotations: Dict[str, Any] = dict() # Get these two objects to make access easier # pylint: disable=protected-access @@ -301,7 +302,7 @@ def describe(self, template: str = 'populationview_default.txt', "parent": self.parent.label, "mask": self.mask, "size": self.size} - context.update(self.__annotations) + context.update(self.annotations) return descriptions.render(engine, template, context) def find_units(self, variable: str) -> str: diff --git a/spynnaker/pyNN/models/projection.py b/spynnaker/pyNN/models/projection.py index c2e3929476d..88a46cfc48b 100644 --- a/spynnaker/pyNN/models/projection.py +++ b/spynnaker/pyNN/models/projection.py @@ -208,6 +208,12 @@ def __init__( if isinstance(pre_vertex, SpikeSourcePoissonVertex): pre_vertex.add_outgoing_projection(self) + SpynnakerDataView.write_pynn_report( + "Projection(pre={}, post={}, connector={}, synapse_type={}, " + "source={}, receptor_type={}, space={}, label={})", + pre_synaptic_population.label, post_synaptic_population.label, + connector, synapse_type, source, receptor_type, space, label) + @staticmethod def __check_population(param: _Pop) -> bool: """ diff --git a/spynnaker/pyNN/models/spike_source/spike_source_array.py b/spynnaker/pyNN/models/spike_source/spike_source_array.py index ff515e5e8da..cbf254d3bd2 100644 --- a/spynnaker/pyNN/models/spike_source/spike_source_array.py +++ b/spynnaker/pyNN/models/spike_source/spike_source_array.py @@ -55,3 +55,6 @@ def create_vertex( @property def _spike_times(self) -> Spikes: return self.__spike_times + + def __str__(self) -> str: + return f"SpikeSourceArray(spike_times={self.__spike_times})" diff --git a/spynnaker/pyNN/models/spike_source/spike_source_poisson.py b/spynnaker/pyNN/models/spike_source/spike_source_poisson.py index 433f1be9c49..8ea815ad851 100644 --- a/spynnaker/pyNN/models/spike_source/spike_source_poisson.py +++ b/spynnaker/pyNN/models/spike_source/spike_source_poisson.py @@ -75,3 +75,7 @@ def create_vertex( n_neurons, label, seed, neurons_per_core, self, rate=self.__rate, start=self.__start, duration=self.__duration, max_rate=max_rate, splitter=splitter, n_colour_bits=n_colour_bits) + + def __str__(self) -> str: + return (f"SpikeSourcePoisson(rate={self.__rate}, start={self.__start}," + f" duration={self.__duration})") diff --git a/spynnaker/pyNN/models/spike_source/spike_source_poisson_variable.py b/spynnaker/pyNN/models/spike_source/spike_source_poisson_variable.py index a34195a1fc2..9c907614388 100644 --- a/spynnaker/pyNN/models/spike_source/spike_source_poisson_variable.py +++ b/spynnaker/pyNN/models/spike_source/spike_source_poisson_variable.py @@ -70,3 +70,9 @@ def create_vertex( return SpikeSourcePoissonVertex( n_neurons, label, seed, max_atoms, self, rates=self._rates, starts=self._starts, durations=self._durations, splitter=splitter) + + def __str__(self) -> str: + return f"SpikeSourcePoissonVariable(" \ + f"rates={self._rates}, " \ + f"starts={self._starts}, " \ + f"durations={self._durations})" diff --git a/spynnaker/pyNN/models/utility_models/spike_injector/spike_injector.py b/spynnaker/pyNN/models/utility_models/spike_injector/spike_injector.py index f7c78fc21cf..2d5aaebe891 100644 --- a/spynnaker/pyNN/models/utility_models/spike_injector/spike_injector.py +++ b/spynnaker/pyNN/models/utility_models/spike_injector/spike_injector.py @@ -51,3 +51,6 @@ def create_vertex( return SpikeInjectorVertex( n_neurons, label, port, virtual_key, reserve_reverse_ip_tag, splitter, max_atoms_per_core) + + def __str__(self) -> str: + return "SpikeInjector()" diff --git a/spynnaker/pyNN/spinnaker.py b/spynnaker/pyNN/spinnaker.py index 465d792e984..e94f625763b 100644 --- a/spynnaker/pyNN/spinnaker.py +++ b/spynnaker/pyNN/spinnaker.py @@ -192,7 +192,7 @@ def clear(self) -> None: """ Clear the current recordings and reset the simulation. """ - self.recorders = set() + self.__recorders = set() self.reset() # Stop any currently running SpiNNaker application diff --git a/spynnaker/pyNN/spynnaker.cfg b/spynnaker/pyNN/spynnaker.cfg index 569ce8bede6..e62ef60f26f 100644 --- a/spynnaker/pyNN/spynnaker.cfg +++ b/spynnaker/pyNN/spynnaker.cfg @@ -34,6 +34,10 @@ write_redundant_packet_count_report = Info @write_redundant_packet_count_report = Writes a report showing how many redundant packets where recorded. path_redundant_packet_count_report = redundant_packet_count.rpt +write_pynn_report = Info +@write_pynn_report = Writes a report on the PyNN calls made. +path_pynn_report = pynn_report.txt + [Simulation] @ = The section covers settings which control how the models behave. diff --git a/unittests/mocks.py b/unittests/mocks.py index 9cb30b210f5..ec140c59432 100644 --- a/unittests/mocks.py +++ b/unittests/mocks.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import List, Mapping, Optional, Sequence, Tuple, Union +from typing import List, Mapping, Optional, Sequence, Tuple, Union, Dict from numpy.typing import NDArray from spinn_utilities.overrides import overrides @@ -30,7 +30,7 @@ from spynnaker.pyNN.models.neuron import PopulationVertex, \ AbstractPyNNNeuronModel from spynnaker.pyNN.models.neuron.implementations import ( - AbstractNeuronImpl, AbstractStandardNeuronComponent) + AbstractNeuronImpl, AbstractStandardNeuronComponent, ModelParameter) from spynnaker.pyNN.models.neuron.input_types import AbstractInputType from spynnaker.pyNN.models.neuron.synapse_dynamics import ( AbstractSynapseDynamics) @@ -268,6 +268,10 @@ def add_state_variables( self, state_variables: RangeDictionary[float]) -> None: pass + @overrides(AbstractStandardNeuronComponent.get_param_values) + def get_param_values(self) -> Dict[str, ModelParameter]: + return dict() + class MockInputType(MockNeuronComponent, AbstractInputType): diff --git a/unittests/test_populations/test_vertex.py b/unittests/test_populations/test_vertex.py index 6dd0a561810..de28c0a993c 100644 --- a/unittests/test_populations/test_vertex.py +++ b/unittests/test_populations/test_vertex.py @@ -13,6 +13,7 @@ # limitations under the License. import pytest +from typing import Dict import pyNN.spiNNaker as sim from spinn_utilities.overrides import overrides @@ -25,7 +26,7 @@ from spynnaker.pyNN.models.defaults import default_initial_values from spynnaker.pyNN.models.neuron.implementations import ( - AbstractStandardNeuronComponent) + AbstractStandardNeuronComponent, ModelParameter) from unittests.mocks import ( MockInputType, MockSynapseType, MockThresholdType) @@ -37,6 +38,10 @@ def __init__(self, foo: int, bar: int): self._foo = foo self._bar = bar + @overrides(AbstractStandardNeuronComponent.get_param_values) + def get_param_values(self) -> Dict[str, ModelParameter]: + return {"foo": self._foo, "bar": self._bar} + @overrides(AbstractStandardNeuronComponent.add_parameters) def add_parameters(self, parameters: RangeDictionary[float]) -> None: pass