"""
.. module:: world
:platform: Linux, Unix, Windows
:synopsis: Provides classes for representing a world and its edges.
.. moduleauthor:: Robert Grant <rhgrant10@gmail.com>
"""
import json
[docs]class World:
"""The nodes and edges of a particular problem.
Each :class:`World` is created from a list of nodes, a length function, and
optionally, a name and a description. Additionally, each :class:`World` has
a UID. The length function must accept nodes as its first two parameters,
and is responsible for returning the distance between them. It is the
responsibility of the :func:`create_edges` to generate the required
:class:`Edge`\s and initialize them with the correct *length* as returned
by the length function.
Once created, :class:`World` objects convert the actual nodes into node
IDs, since solving does not rely on the actual data in the nodes. These are
accessible via the :attr:`nodes` property. To access the actual nodes,
simply pass an ID obtained from :attr:`nodes` to the :func:`data` method,
which will return the node associated with the specified ID.
:class:`Edge`\s are accessible in much the same way, except two node IDs
must be passed to the :func:`data` method to indicate which nodes start and
end the :class:`Edge`. For example:
.. code-block:: python
ids = world.nodes
assert len(ids) > 1
node0 = world.data(ids[0])
node1 = world.data(ids[1])
edge01 = world.data(ids[0], ids[1])
assert edge01.start == node0
assert edge01.end == node1
The :func:`reset_pheromone` method provides an easy way to reset the
pheromone levels of every :class:`Edge` contained in a :class:`World` to a
given *level*. It should be invoked before attempting to solve a
:class:`World` unless a "blank slate" is not desired. Also note that it
should *not* be called between iterations of the :class:`Solver` because it
effectively erases the memory of the :class:`Ant` colony solving it.
:param list nodes: a list of nodes
:param callable lfunc: a function that calculates the distance between
two nodes
:param str name: the name of the world (default is "world#", where
"#" is the ``uid`` of the world)
:param str description: a description of the world (default is None)
"""
uid = 0
def __init__(self, nodes, lfunc, **kwargs):
self.uid = self.__class__.uid
self.__class__.uid += 1
self.name = kwargs.get('name', 'world{}'.format(self.uid))
self.description = kwargs.get('description', None)
self._nodes = nodes
self.lfunc = lfunc
self.edges = self.create_edges()
@property
def nodes(self):
"""Node IDs."""
return list(range(len(self._nodes)))
[docs] def create_edges(self):
"""Create edges from the nodes.
The job of this method is to map node ID pairs to :class:`Edge`
instances that describe the edge between the nodes at the given
indices. Note that all of the :class:`Edge`\s are created within this
method.
:return: a mapping of node ID pairs to :class:`Edge` instances.
:rtype: :class:`dict`
"""
edges = {}
for m in self.nodes:
for n in self.nodes:
a, b = self.data(m), self.data(n)
if a != b:
edge = Edge(a, b, length=self.lfunc(a, b))
edges[m, n] = edge
return edges
[docs] def reset_pheromone(self, level=0.01):
"""Reset the amount of pheromone on every edge to some base *level*.
Each time a new set of solutions is to be found, the amount of
pheromone on every edge should be equalized to ensure un-biased initial
conditions.
:param float level: amount of pheromone to set on each edge
(default=0.01)
"""
for edge in self.edges.values():
edge.pheromone = level
[docs] def data(self, idx, idy=None):
"""Return the node data of a single id or the edge data of two ids.
If only *idx* is specified, return the node with the ID *idx*. If *idy*
is also specified, return the :class:`Edge` between nodes with indices
*idx* and *idy*.
:param int idx: the id of the first node
:param int idy: the id of the second node (default is None)
:return: the node with ID *idx* or the :class:`Edge` between nodes
with IDs *idx* and *idy*.
:rtype: node or :class:`Edge`
"""
try:
if idy is None:
return self._nodes[idx]
else:
return self.edges[idx, idy]
except IndexError:
return None
[docs]class Edge:
"""This class represents the link between starting and ending nodes.
In addition to *start* and *end* nodes, every :class:`Edge` has *length*
and *pheromone* properties. *length* represents the static, *a priori*
information, whereas *pheromone* level represents the dynamic, *a
posteriori* information.
:param node start: the node at the start of the :class:`Edge`
:param node end: the node at the end of the :class:`Edge`
:param float length: the length of the :class:`Edge` (default=1)
:param float pheromone: the amount of pheromone on the :class:`Edge`
(default=0.1)
"""
def __init__(self, start, end, length=None, pheromone=None):
self.start = start
self.end = end
self.length = 1 if length is None else length
self.pheromone = 0.1 if pheromone is None else pheromone
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.__dict__ == other.__dict__
return False