Source code for neet.statespace

"""
.. currentmodule:: neet.statespace

.. testsetup:: statespace

    from neet.statespace import StateSpace

State Space
===========

A fundamental property of networks is their state space. The
:class:`StateSpace` class provides a type for representing such a state
space. It has anumber of convenient properties, in particular

1. it is iterable, :meth:`StateSpace.__iter__`
2. you can ask if it contains a particular state,
   :meth:`StateSpace.__contains__`
3. it can :meth:`StateSpace.encode` states as integers, and
   :meth:`StateSpace.decode` integers into states

All of this together facilitates the clean implementation of many of the core
functions of :mod:`neet`.

API Documentation
-----------------
"""
from .python import long


[docs]class StateSpace(object): """ :class:`StateSpace` represents the state space of a network model. It may be either uniform, i.e. all nodes have the same base, or non-uniform. """
[docs] def __init__(self, spec, base=None): """ Initialize the state spec in accordance with the provided ``spec`` and base ``base``. .. rubric:: Examples of Uniform State Spaces: .. doctest:: statespace >>> spec = StateSpace(5) >>> (spec.is_uniform, spec.ndim, spec.base) (True, 5, 2) >>> spec = StateSpace(3, base=3) >>> (spec.is_uniform, spec.ndim, spec.base) (True, 3, 3) >>> spec = StateSpace([2, 2, 2]) >>> (spec.is_uniform, spec.ndim, spec.base) (True, 3, 2) .. rubric:: Examples of Non-Uniform State Spaces: .. doctest:: statespace >>> spec = StateSpace([2, 3, 4]) >>> (spec.is_uniform, spec.base, spec.ndim) (False, [2, 3, 4], 3) :param spec: the number of nodes or an array of node bases :type spec: int or list :param base: the base of the network nodes (ignored if ``spec`` is a list) :raises TypeError: if ``spec`` is neither an int nor a list of ints :raises TypeError: if ``base`` is neither ``None`` nor an int :raises ValueError: if ``base`` is negative or zero :raises ValueError: if any element of ``spec`` is negative or zero :raises ValueError: if ``spec`` is empty """ if isinstance(spec, int): if spec < 1: raise ValueError("ndim cannot be zero or negative") if base is None: base = 2 elif not isinstance(base, int): raise TypeError("base must be an int") elif base < 1: raise ValueError("base must be positive, nonzero") self.__is_uniform = True self.__ndim = spec self.__base = base self.__volume = base**spec elif isinstance(spec, list): if len(spec) == 0: raise ValueError("bases cannot be an empty") else: self.__is_uniform = True self.__volume = 1 first_base = spec[0] if base is not None and first_base != base: raise ValueError("base does not match base of spec") for spec_base in spec: if not isinstance(spec_base, int): raise TypeError("spec must be a list of ints") elif spec_base < 1: msg = "spec may only contain positive elements" raise ValueError(msg) if self.__is_uniform and spec_base != first_base: self.__is_uniform = False if base is not None: raise ValueError("b does not match base of spec") self.__volume *= spec_base self.__ndim = len(spec) if self.__is_uniform: self.__base = first_base else: self.__base = spec[:] else: raise TypeError("spec must be an int or a list")
@property def ndim(self): """ Get the dimensionality of the state space. """ return self.__ndim @property def base(self): """ Get the base of each direction of the state space. If the state space is not uniform, the result is a list of bases (one for each dimension). Otherwise, the result is an integer. :return: a list of bases, one for each dimension """ return self.__base @property def volume(self): """ Get the volume of the state space. """ return self.__volume @property def is_uniform(self): """ Get whether every direction in the state space has the same base. """ return self.__is_uniform
[docs] def __iter__(self): """ Iterate over the states in the state space .. rubric:: Examples of Boolean Spaces .. doctest:: statespace >>> list(StateSpace(1)) [[0], [1]] >>> list(StateSpace(2)) [[0, 0], [1, 0], [0, 1], [1, 1]] >>> list(StateSpace(3)) [[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0], [0, 0, 1], [1, 0, 1], [0, 1, 1], [1, 1, 1]] .. rubric:: Examples of Non-Boolean Spaces .. doctest:: statespace >>> list(StateSpace(1, base=3)) [[0], [1], [2]] >>> list(StateSpace(2, base=4)) [[0, 0], [1, 0], [2, 0], [3, 0], [0, 1], [1, 1], [2, 1], [3, 1], [0, 2], [1, 2], [2, 2], [3, 2], [0, 3], [1, 3], [2, 3], [3, 3]] .. rubric:: Examples of Non-Uniform Spaces .. doctest:: statespace >>> list(StateSpace([1,2,3])) [[0, 0, 0], [0, 1, 0], [0, 0, 1], [0, 1, 1], [0, 0, 2], [0, 1, 2]] >>> list(StateSpace([3,4])) [[0, 0], [1, 0], [2, 0], [0, 1], [1, 1], [2, 1], [0, 2], [1, 2], [2, 2], [0, 3], [1, 3], [2, 3]] :yields: each possible state in the state space """ state = [0] * self.ndim yield state[:] i = 0 while i != self.ndim: base = self.__base if self.__is_uniform else self.__base[i] if state[i] + 1 < base: state[i] += 1 for j in range(i): state[j] = 0 i = 0 yield state[:] else: i += 1
[docs] def __contains__(self, states): """ Determine if a state is in the state space .. rubric:: Examples: .. doctest:: statespace >>> state_space = StateSpace(3) >>> [0, 0, 0] in state_space True >>> [0, 0] in state_space False >>> [1, 2, 1] in state_space False .. doctest:: statespace >>> [0, 2, 1] in StateSpace([2, 3, 2]) True >>> [0, 1] in StateSpace([2, 2, 3]) False >>> [1, 1, 6] in StateSpace([2, 3, 4]) False :param states: the one-dimensional sequence of node states :returns: ``True`` if the ``states`` are valid """ try: if len(states) != self.ndim: return False if self.is_uniform: for state in states: if state not in range(self.base): return False else: for state, base in zip(states, self.base): if state not in range(base): return False except TypeError: return False except IndexError: return False return True
def _unsafe_encode(self, state): """ Encode a state as an integer consistent with the state space, without checking the validity of the arguments. The encoding is such that, for example, the state [1, 0, 0] will correspond to 1; the state [1, 1, 0] will correspond to 3. .. rubric:: Examples: .. doctest:: statespace >>> space = StateSpace(3, base=2) >>> [space._unsafe_encode(s) for s in space] [0, 1, 2, 3, 4, 5, 6, 7] .. doctest:: statespace >>> space = StateSpace([2,3,4]) >>> [space._unsafe_encode(s) for s in space] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23] :param state: the state to encode :type state: list :returns: a unique integer encoding of the state :raises ValueError: if ``state`` has an incorrect length """ encoded, place = long(0), long(1) base = self.__base if self.is_uniform: for x in state: encoded += place * x place *= base else: for (x, b) in zip(state, base): encoded += place * x place *= b return long(encoded)
[docs] def encode(self, state): """ Encode a state as an integer consistent with the state space. The encoding is such that, for example, the state [1, 0, 0] will correspond to 1; the state [1, 1, 0] will correspond to 3. .. rubric:: Examples: .. doctest:: statespace >>> space = StateSpace(3, base=2) >>> [space.encode(s) for s in space] [0, 1, 2, 3, 4, 5, 6, 7] .. doctest:: statespace >>> space = StateSpace([2,3,4]) >>> [space.encode(s) for s in space] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23] :param state: the state to encode :type state: list :returns: a unique integer encoding of the state :raises ValueError: if ``state`` has an incorrect length """ if state not in self: raise ValueError("state is not in state space") return self._unsafe_encode(state)
[docs] def decode(self, encoded): """ Decode an integer into a state in accordance with the state space. .. rubric:: Examples: .. doctest:: statespace >>> space = StateSpace(3) >>> list(space) [[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0], [0, 0, 1], [1, 0, 1], [0, 1, 1], [1, 1, 1]] >>> [space.decode(x) for x in range(space.volume)] [[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0], [0, 0, 1], [1, 0, 1], [0, 1, 1], [1, 1, 1]] .. doctest:: statespace >>> space = StateSpace([2,3]) >>> list(space) [[0, 0], [1, 0], [0, 1], [1, 1], [0, 2], [1, 2]] >>> [space.decode(x) for x in range(space.volume)] [[0, 0], [1, 0], [0, 1], [1, 1], [0, 2], [1, 2]] :param encoded: the encoded state :type encoded: int :returns: the decoded state as a list """ state = [0] * self.__ndim base = self.__base if self.is_uniform: b = base for i in range(self.__ndim): state[i] = encoded % b encoded = int(encoded / b) else: for i in range(self.__ndim): b = base[i] state[i] = encoded % b encoded = int(encoded / b) return state