Source code for tvb.simulator.descriptors

# -*- coding: utf-8 -*-
# TheVirtualBrain-Scientific Package. This package holds all simulators, and
# analysers necessary to run brain-simulations. You can use it stand alone or
# in conjunction with TheVirtualBrain-Framework Package. See content of the
# documentation-folder for more details. See also
# (c) 2012-2023, Baycrest Centre for Geriatric Care ("Baycrest") and others
# This program is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software Foundation,
# either version 3 of the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE.  See the GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along with this
# program.  If not, see <>.
# When using The Virtual Brain for scientific publications, please cite it as explained here:

Data descriptors for declaring workspace for algorithms and checking usage.

.. moduleauthor:: Marmaduke Woodman <>


import numpy
import collections
import weakref
import six
from .common import get_logger

LOG = get_logger(__name__)

# StaticAttr prevents descriptors from placing instance-owned, descriptor storage

[docs]class StaticAttr(object): "Base class which requires all attributes to be declared at class level." def __setattr__(self, name, value): attr_exists = hasattr(self, name) if not hasattr(self, name): raise AttributeError('%r has no attr %r.' % (self, name)) else: super(StaticAttr, self).__setattr__(name, value)
[docs]class ImmutableAttrError(AttributeError): "Error due to modifying an immutable attribute." pass
[docs]class IncorrectTypeAttrError(AttributeError): "Error due to using incorrect type to set attribute value." pass
[docs]class NDArray(StaticAttr): "Data descriptor for a NumPy array, with type, mutability and shape checking." # Owner can provide array constructor via _array_ctor attr, i.e. NumPy or PyOpenCL, etc. ? State = collections.namedtuple('State', 'array initialized') shape, dtype, read_only, instance_state = (), None, True, {} def __init__(self, shape, dtype, read_only=True): self.shape = shape # may have strings which eval in owner ns self.dtype = dtype self.read_only = read_only self.instance_state = weakref.WeakKeyDictionary() def _make_array(self, instance): shape = [] for dim in self.shape: if isinstance(dim, str): dim = getattr(instance, dim) elif isinstance(dim, Dim): if instance in dim.instance_state: dim = dim.instance_state[instance].value else: raise AttributeError('Dimension referenced before definition.') else: raise TypeError('expect int, str but found %r' % (type(dim), )) shape.append(dim) array = numpy.empty(shape, self.dtype) if self.read_only and hasattr(array, 'setflags'): array.setflags(write=False) return array def _get_or_create_state(self, instance): if instance not in self.instance_state: array = self._make_array(instance) self.instance_state[instance] = NDArray.State(array, False) return self.instance_state[instance] def __get__(self, instance, _): if instance is None: LOG.debug('NDArray returning self for None instance.') return self else: return self._get_or_create_state(instance).array def __set__(self, instance, value): state = self._get_or_create_state(instance) if self.read_only: if state.initialized: raise ImmutableAttrError('Cannot modify an immutable ndarray.') else: state.array.setflags(write=True) # set with [:] to ensure shape compat and safe type coercion _, value = numpy.broadcast_arrays(state.array, value) state.array[:] = value if self.read_only: state.array.setflags(write=False) if not state.initialized: self.instance_state[instance] = NDArray.State(state.array, True)
[docs]class Final(object): "A descriptor for an attribute, possibly type-checked, that once initialized, cannot be changed." State = collections.namedtuple('State', 'value initialized') def __init__(self, type=None): self.instance_state = weakref.WeakKeyDictionary() self.type = type def _get_or_create_state(self, instance): if instance not in self.instance_state: self.instance_state[instance] = Final.State(None, False) return self.instance_state[instance] def _correct_type(self, value): return isinstance(value, self.type) def __set__(self, instance, value): state = self._get_or_create_state(instance) # type: Final.State if state.initialized: raise AttributeError('final attribute cannot be set.') else: if self.type and not self._correct_type(value): raise AttributeError('value %r does not match expected type %r' % (value, self.type)) self.instance_state[instance] = Final.State(value, True) def __get__(self, instance, owner): if instance is None: LOG.debug('Final returning self for None instance.') return self else: return self._get_or_create_state(instance).value
[docs]class Dim(Final): "Specialization of Final to int/long type." def __init__(self): super(Dim, self).__init__(int) def _correct_type(self, value): return isinstance(value, six.integer_types) \ or numpy.issubdtype(type(value), numpy.integer)
[docs]class Workspace(StaticAttr): pass