-
Notifications
You must be signed in to change notification settings - Fork 47
Pynn report #1656
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Pynn report #1656
Changes from all commits
d971e90
4906a4f
7257664
b48ea1b
4d3826a
ee11b57
8fa96ae
7b99d2d
dd4753e
4416aa8
3071469
9491a05
42f93aa
7559a13
7f07a7f
282a22d
1f3980d
2c1c4a5
99adaab
90a1613
cd62501
bc521fa
c3a22c7
faffd70
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -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,21 +521,23 @@ 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. | ||||||
|
|
||||||
| :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 | ||||||
rowleya marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
|
||||||
|
|
||||||
| 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) | ||||||
rowleya marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| 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: | ||||||
|
||||||
| if annotations: | |
| if annotations and hasattr(source, "annotate"): |
Copilot
AI
Mar 4, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
record() is annotated/documented to return a neo.Block, but it never returns anything. Either return the created/extracted block (PyNN API expectation) or update the return type and docstring to indicate None is returned.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do not cache cfg data |
||
|
|
||
| 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, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be in a utils / reports cfile not in a Data file |
||
| **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") | ||
rowleya marked this conversation as resolved.
Show resolved
Hide resolved
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
connect()is documented as returning a newProjection, but the function signature is-> Noneand the createdProjection(...)is not returned. Either return the createdProjection(and update the return type accordingly) or remove the:returns:line from the docstring to avoid misleading API users.