Skip to content

Commit

Permalink
Merge pull request #1528 from SpiNNakerManchester/AbstractProvidesDef…
Browse files Browse the repository at this point in the history
…aults

Abstract provides defaults
  • Loading branch information
Christian-B authored Jan 30, 2025
2 parents d3a454a + 6c62c61 commit 720bdc4
Show file tree
Hide file tree
Showing 10 changed files with 133 additions and 109 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
AbstractOneAppOneMachineVertex)
from spinn_front_end_common.abstract_models import (
AbstractVertexWithEdgeToDependentVertices)
from spynnaker.pyNN.models.defaults import defaults
from spynnaker.pyNN.models.defaults import AbstractProvidesDefaults
from spynnaker.pyNN.models.common import PopulationApplicationVertex
from .machine_munich_motor_device import MachineMunichMotorDevice

Expand All @@ -35,11 +35,11 @@ def __init__(self, spinnaker_link_id, board_address=None):
label="External Munich Motor", board_address=board_address)


@defaults
class MunichMotorDevice(
AbstractOneAppOneMachineVertex,
AbstractVertexWithEdgeToDependentVertices,
PopulationApplicationVertex):
PopulationApplicationVertex,
AbstractProvidesDefaults):
"""
An Omnibot motor control device. This has a real vertex and an
external device vertex.
Expand Down
45 changes: 3 additions & 42 deletions spynnaker/pyNN/models/abstract_pynn_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,20 @@
from collections import defaultdict
import sys
from typing import (
Any, Callable, cast, Dict, FrozenSet, Optional, Mapping, Sequence, Tuple,
TYPE_CHECKING)
Any, Dict, Optional, Sequence, Tuple, TYPE_CHECKING)
import numpy
from pyNN import descriptions
from spinn_utilities.classproperty import classproperty
from spinn_utilities.abstract_base import (
AbstractBase, abstractmethod)
from spynnaker.pyNN.models.defaults import get_map_from_init
from spynnaker.pyNN.models.defaults import AbstractProvidesDefaults
from spynnaker.pyNN.exceptions import SpynnakerException
if TYPE_CHECKING:
from spynnaker.pyNN.models.common.population_application_vertex import (
PopulationApplicationVertex)


class AbstractPyNNModel(object, metaclass=AbstractBase):
class AbstractPyNNModel(AbstractProvidesDefaults, metaclass=AbstractBase):
"""
A Model that can be passed in to a Population object in PyNN.
"""
Expand Down Expand Up @@ -89,44 +88,6 @@ def absolute_max_atoms_per_core( # pylint: disable=no-self-argument
"""
return sys.maxsize

@staticmethod
def __get_init_params_and_svars(the_cls: type) -> Tuple[
Callable, Optional[FrozenSet[str]], Optional[FrozenSet[str]]]:
init = getattr(the_cls, "__init__")
while hasattr(init, "_method"):
init = getattr(init, "_method")
params = None
if hasattr(init, "_parameters"):
params = getattr(init, "_parameters")
svars = None
if hasattr(init, "_state_variables"):
svars = getattr(init, "_state_variables")
return init, params, svars

@classproperty
def default_parameters( # pylint: disable=no-self-argument
cls) -> Mapping[str, Any]:
"""
Get the default values for the parameters of the model.
:rtype: dict(str, Any)
"""
init, params, svars = cls.__get_init_params_and_svars(cast(type, cls))
return get_map_from_init(init, skip=svars, include=params)

@classproperty
def default_initial_values( # pylint: disable=no-self-argument
cls) -> Mapping[str, Any]:
"""
Get the default initial values for the state variables of the model.
:rtype: dict(str, Any)
"""
init, params, svars = cls.__get_init_params_and_svars(cast(type, cls))
if params is None and svars is None:
return {}
return get_map_from_init(init, skip=params, include=svars)

@classmethod
def get_parameter_names(cls) -> Sequence[str]:
"""
Expand Down
106 changes: 105 additions & 1 deletion spynnaker/pyNN/models/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
import inspect
import logging
from types import MappingProxyType
from typing import Any, Callable, FrozenSet, Iterable, List, Mapping, Optional
from typing import (
Any, Callable, FrozenSet, Iterable, List, Mapping, Optional)
from spinn_utilities.classproperty import classproperty
from spinn_utilities.log import FormatAdapter

logger = FormatAdapter(logging.getLogger(__name__))
Expand Down Expand Up @@ -156,6 +158,8 @@ def defaults(cls: type) -> type:
If neither are specified, it is assumed that all default arguments are
parameters.
"""
logger.warning("@defaults is deprecated! "
"Extend AbstractProvidesDefaults instead")
if not inspect.isclass(cls):
raise TypeError(f"{cls} is not a class")
if not hasattr(cls, "__init__"):
Expand All @@ -182,3 +186,103 @@ def defaults(cls: type) -> type:
cls.default_parameters = get_map_from_init(init, include=params)
cls.default_initial_values = get_map_from_init(init, include=svars)
return cls


class AbstractProvidesDefaults(object):
"""
Provides the default_parameters and default_initial_values properties
These will be filled in based on the @default_parameters and
@default_initial_values decorators with values read from the init.
"""

@classmethod
def __fill_in_defaults(cls):
"""
Fills in default_parameters and default_initial_values attributes
"""
# get the init method
init = getattr(cls, "__init__")
# Find the real method as there may be decorators
while hasattr(init, "_method"):
init = getattr(init, "_method")

# read the values from the init method
init_args = inspect.getfullargspec(init)
n_defaults = 0 if init_args.defaults is None else len(
init_args.defaults)
n_args = 0 if init_args.args is None else len(init_args.args)
default_args = ([] if init_args.args is None else
init_args.args[n_args - n_defaults:])
if init_args.defaults is None:
default_values = []
else:
default_values = init_args.defaults

# get the keys based on the decorators
if hasattr(init, "_parameters"):
params = getattr(init, "_parameters")
if hasattr(init, "_state_variables"):
svars = getattr(init, "_state_variables")
assert len(params.intersection(svars)) == 0
else:
svars = frozenset(svar for svar in default_args
if svar not in params)
else:
if hasattr(init, "_state_variables"):
svars = getattr(init, "_state_variables")
params = frozenset(param for param in default_args
if param not in svars)
else:
svars = frozenset()
params = set(default_args)

# Check all decorator values used
_check_args(params.union(svars), default_args, init)

# fill in the defaults so this method is only called once
cls.default_parameters: Mapping[str, Any] = {}
cls.default_initial_values: Mapping[str, Any] = {}
for arg, value in zip(default_args, default_values):
if arg in params:
cls.default_parameters[arg] = value
elif arg in svars:
cls.default_initial_values[arg] = value
cls.default_parameters = MappingProxyType(cls.default_parameters)
cls.default_initial_values = (
MappingProxyType(cls.default_initial_values))

@classproperty
def default_parameters( # pylint: disable=no-self-argument
cls) -> Mapping[str, Any]:
"""
Get the default values for the parameters of the model.
If a @default_parameters decorator is used
this will be the init default values for those keys
If no @default_parameters decorator is used
this will be all the init parameters with a default value
less any defined in @default_initial_values
"""
cls.__fill_in_defaults()
return cls.default_parameters

@classproperty
def default_initial_values( # pylint: disable=no-self-argument
cls) -> Mapping[str, Any]:
"""
Get the default initial values for the state variables of the model.
If @default_initial_values decorator is used
this will be the init default values for those keys
If no @default_initial_values decorator is used
but a @default_parameters decorator was used
this will be all the init parameters with a default value
less any defined in @default_parameters
If neither decorator is used this will be an empty Mapping
"""
cls.__fill_in_defaults()
return cls.default_initial_values
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@
# limitations under the License.

from spynnaker.pyNN.exceptions import SpynnakerException
from spynnaker.pyNN.models.defaults import defaults, default_initial_values
from spynnaker.pyNN.models.defaults import (
AbstractProvidesDefaults, default_initial_values)


# pylint: disable=wrong-spelling-in-docstring
@defaults
class EIFConductanceAlphaPopulation(object):
class EIFConductanceAlphaPopulation(AbstractProvidesDefaults):
"""
Exponential integrate and fire neuron with spike triggered and
sub-threshold adaptation currents (isfa, ista reps.)
Expand Down
6 changes: 3 additions & 3 deletions spynnaker/pyNN/models/neuron/builds/hh_cond_exp.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
# limitations under the License.

from spynnaker.pyNN.exceptions import SpynnakerException
from spynnaker.pyNN.models.defaults import defaults, default_initial_values
from spynnaker.pyNN.models.defaults import (
AbstractProvidesDefaults, default_initial_values)


@defaults
class HHCondExp(object):
class HHCondExp(AbstractProvidesDefaults):
"""
Single-compartment Hodgkin-Huxley model with exponentially decaying
current input.
Expand Down
6 changes: 3 additions & 3 deletions spynnaker/pyNN/models/neuron/builds/if_cond_alpha.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
# limitations under the License.

from spynnaker.pyNN.exceptions import SpynnakerException
from spynnaker.pyNN.models.defaults import defaults, default_initial_values
from spynnaker.pyNN.models.defaults import (
AbstractProvidesDefaults, default_initial_values)


@defaults
class IFCondAlpha(object):
class IFCondAlpha(AbstractProvidesDefaults):
"""
Leaky integrate and fire neuron with an alpha-shaped current input.
Expand Down
6 changes: 3 additions & 3 deletions spynnaker/pyNN/models/neuron/builds/if_facets_hardware1.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
# limitations under the License.

from spynnaker.pyNN.exceptions import SpynnakerException
from spynnaker.pyNN.models.defaults import defaults, default_initial_values
from spynnaker.pyNN.models.defaults import (
AbstractProvidesDefaults, default_initial_values)


@defaults
class IFFacetsConductancePopulation(object):
class IFFacetsConductancePopulation(AbstractProvidesDefaults):
"""
Leaky integrate and fire neuron with conductance-based synapses and
fixed threshold as it is resembled by the FACETS Hardware Stage 1.
Expand Down
3 changes: 1 addition & 2 deletions unittests/model_tests/neuron/test_abstract_neuron_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@
from spynnaker.pyNN.models.abstract_pynn_model import AbstractPyNNModel
from spynnaker.pyNN.models.neuron.abstract_pynn_neuron_model import (
AbstractPyNNNeuronModel)
from spynnaker.pyNN.models.defaults import default_initial_values, defaults
from spynnaker.pyNN.models.defaults import default_initial_values
from spynnaker.pyNN.exceptions import SpynnakerException


@defaults
class _MyPyNNModelImpl(AbstractPyNNModel):

default_population_parameters = {}
Expand Down
Loading

0 comments on commit 720bdc4

Please sign in to comment.