class Artifact:
    """Base class for artifacts.

    A wrapper around the actual artifact object
    (:attr:`~creamas.core.artifact.Artifact.obj`) which holds information about
    the creator, framings and evaluations of the artifact.
    """

    def __init__(self, creator, obj, domain=int):
        if hasattr(creator, 'name'):
            self._creator = creator.name
        else:
            self._creator = creator
        self._obj = obj
        self._domain = domain
        self._evals = {}
        self._framings = {}
        self._feature_values = {}  # Objective feature values for each feature.

    @property
    def creator(self):
        """The name of the agent which created the artifact.
        """
        return self._creator

    @property
    def obj(self):
        """Artifact object itself.
        """
        return self._obj

    @property
    def domain(self):
        """Domain of the artifact. Domain must match feature's possible domains
        at evaluation time, or None is returned.
        """
        return self._domain

    @property
    def evals(self):
        """Dictionary of evaluations for the artifact.

        Keys are the names of the evaluating agents and values are their
        actual evaluations.
        """
        return self._evals

    @property
    def framings(self):
        """Dictionary of framings for the artifacts.

        Keys are the names of the framing agents and values are their
        actual framings.
        """
        return self._framings

        self._feature_values = {}    # Objective feature values for each feature.

    @property
    def feature_values(self):
        """Values for all features extracted from the artifact.
        """
        return self._feature_values

    def add_feature_value(self, feat, val):
        """Add extracted value for the given feature string.
        """
        self._feature_values[feat] = val

    def get_feature_value(self, feat):
        """Return value for the given feature string, or ``None`` if it is not found.
        """
        if feat in self._feature_values:
            return self._feature_values[feat]
        return None

    def add_eval(self, agent, e, fr=None):
        """Add or change agent's evaluation of the artifact with given framing
        information.

        :param agent: Name of the agent which did the evaluation.
        :param float e: Evaluation for the artifact.
        :param object fr: Framing information for the evaluation.
        """
        self._evals[agent.name] = e
        self._framings[agent.name] = fr

    def __str__(self):
        return "{}:{}".format(self.creator, self.obj)

    def __eq__(self, other):
        return str(self) == str(other)

    def __hash__(self):
        return hash(str(self)) class Environment(Container):
    """Base environment class inherited from :py:class:`aiomas.Container`.
    """
    @classmethod
    def create(cls, *args, **kwargs):
        """Create a new environment, see :py:func:`~aiomas.Container.create` for details.

        If ``extra_serializers`` keyword argument is None, serializers returned by
        :func:`~creamas.serializers.get_serializers` are added to it.
        """
        extra_ser = kwargs.pop('extra_serializers', None)
        if extra_ser is None:
            extra_ser = get_serializers()
        kwargs.update({'extra_serializers': extra_ser})
        return super().create(*args, **kwargs)

    def __init__(self, base_url, loop, clock, connect_kwargs):
        super().__init__(base_url, loop, clock, connect_kwargs)
        self._age = 0
        self._logger = None
        self._log_folder = None
        self._artifacts = []
        self._candidates = []
        self._name = base_url

        # Try setting the process name to easily recognize the spawned
        # environments with 'ps -x' or 'top'
        try:
            import setproctitle as spt
            spt.setproctitle('Creamas: {}'.format(str(self)))
        except:
            pass

    @property
    def name(self):
        """Name of the environment."""
        return self._name

    @property
    def artifacts(self):
        """Published artifacts for all agents."""
        return self._artifacts

    @property
    def age(self):
        """Age of the environment.
        """
        return self._age

    @age.setter
    def age(self, a):
        self._age = a

    @property
    def logger(self):
        """Logger for the environment.
        """
        return self._logger

    @property
    def log_folder(self):
        """Logging folder for the environment. If set, will create py:class:`creamas.logging.ObjectLogger` for that
        folder.
        """
        return self._log_folder

    @log_folder.setter
    def log_folder(self, _log_folder):
        assert(type(_log_folder) is str)
        self._log_folder = _log_folder
        self._logger = ObjectLogger(self, _log_folder, add_name=True,
                                    init=True)

    def get_agents(self, addr=True, agent_cls=None, include_manager=False):
        """Get agents in the environment.

        :param bool addr: If ``True``, returns only addresses of the agents.
        :param agent_cls: Optional, if specified returns only agents belonging to that particular class.

        :param bool include_manager:
            If ``True`` includes the environment's manager, i.e. the agent in the address
            ``tcp://environment-host:port/0``, to the returned list if the environment has attribute :attr:`manager`.
            If environment does not have :attr:`manager`, then the parameter does nothing.

        :returns: A list of agents in the environment.
        :rtype: list

        .. note::
            Manager agents are excluded from the returned lists of agents by default.
        """
        agents = list(self.agents.dict.values())
        if hasattr(self, 'manager') and self.manager is not None:
            if not include_manager:
                agents = [a for a in agents if a.addr.rsplit('/', 1)[1] != '0']
        if agent_cls is not None:
            agents = [a for a in agents if type(a) is agent_cls]
        if addr:
            agents = [agent.addr for agent in agents]
        return agents

    def get_agent(self, addr):
        """Get agent in given address.

        :raises ValueError: If given address is not part of this environment
        :raises KeyError: If no such agent in the environment
        """
        base_url, agent_number = addr.rsplit('/', 1)
        base_url += "/"
        if base_url != self._base_url:
            raise ValueError("Given address' base URL ({}) does not match with the environment ({})."
                             .format(base_url, self._base_url))
        agent = self.agents.dict[agent_number]
        return agent

    async def trigger_act(self, *args, addr=None, agent=None, **kwargs):
        """Trigger agent to act.

        If *agent* is None, then looks the agent by the address.

        :raises ValueError: if both *agent* and *addr* are None.
        """
        if agent is None and addr is None:
            raise TypeError("Either addr or agent has to be defined.")
        if agent is None:
            agent = self.get_agent(addr)
        self._log(logging.DEBUG, "Triggering agent in {}".format(agent.addr))
        ret = await agent.act(*args, **kwargs)
        return ret

    async def trigger_all(self, *args, **kwargs):
        """Trigger all agents in the environment to act asynchronously.

        :returns: A list of agents' :meth:`act` return values.

        Given arguments and keyword arguments are passed down to each agent's
        :meth:`creamas.core.agent.CreativeAgent.act`.

        .. note::
            The environment's manager agent, i.e. if the environment has :attr:`manager`, is excluded from acting.
        """
        tasks = []
        for a in self.get_agents(addr=False, include_manager=False):
            task = asyncio.ensure_future(self.trigger_act
                                         (*args, agent=a, **kwargs))
            tasks.append(task)
        rets = await asyncio.gather(*tasks)
        return rets

    def is_ready(self):
        """Check if the environment is fully initialized.

        The function is mainly used by the multiprocessing environment managers
        and distributed environments to ensure that the environment has been
        correctly initialized before any other preparations are done for the
        environments or the simulation is started.

        Override the function in the subclasses which need more time consuming
        initialization routines. The function should return True when the
        environment is ready be used in a simulation, or when any
        cross-environment initialization routines can be run. That is, the
        environment is inherently in a coherent state, and can execute orders
        from managers or simulations.

        :rtype: bool
        :returns: This basic implementation returns always True.
        """
        return True

    def create_random_connections(self, n=5):
        """Create random connections for all agents in the environment.

        :param int n: the number of connections for each agent

        Existing agent connections that would be created by chance are not
        doubled in the agent's :attr:`connections`, but count towards
        connections created.
        """
        if type(n) != int:
            raise TypeError("Argument 'n' must be of type int.")
        if n <= 0:
            raise ValueError("Argument 'n' must be greater than zero.")
        for a in self.get_agents(addr=False):
            others = self.get_agents(addr=False)[:]
            others.remove(a)
            shuffle(others)
            for r_agent in others[:n]:
                a.add_connection(r_agent)

    def create_connections(self, connection_map):
        """Create agent connections from a given connection map.

        :param dict connection_map:
            A map of connections to be created. Dictionary where keys are agent addresses and values are lists of
            (addr, attitude)-tuples suitable for :meth:`~creamas.core.agent.CreativeAgent.add_connections`.

        Only connections for agents in this environment are made.
        """
        agents = self.get_agents(addr=False)
        rets = []
        for a in agents:
            if a.addr in connection_map:
                r = a.add_connections(connection_map[a.addr])
                rets.append(r)
        return rets

    def get_connections(self, data=True):
        """Return connections from all the agents in the environment.

        :param bool data: If ``True`` return also the dictionary associated with each connection

        :returns:
            A list of ``(addr, connections)``-tuples, where ``connections`` is a list of addresses agent in ``addr`` is
            connected to. If ``data`` parameter is ``True``, then the ``connections`` list contains tuples of
            ``(nb_addr, data)``-pairs , where ``data`` is a dictionary.

        :rtype: dict

        .. note::
            Environment's manager agent is excluded from the returned list.
        """
        connections = []
        for a in self.get_agents(addr=False):
            c = (a.addr, a.get_connections(data=data))
            connections.append(c)
        return connections

    def clear_connections(self):
        """Clear all connections from the agents in the environment.
        """
        for a in self.get_agents(addr=False):
            a.clear_connections()

    def get_random_agent(self, agent):
        """Return random agent that is not the same as the agent given as a parameter.

        :param agent: Agent that is not wanted to return
        :type agent: :py:class:`~creamas.core.agent.CreativeAgent`
        :returns: random, non-connected, agent from the environment
        :rtype: :py:class:`~creamas.core.agent.CreativeAgent`
        """
        r_agent = choice(self.get_agents(addr=False))
        while r_agent.addr == agent.addr:
            r_agent = choice(self.get_agents(addr=False))
        return r_agent

    def add_artifact(self, artifact):
        """Add artifact with given framing to the environment.

        :param object artifact: Artifact to be added.
        """
        artifact.env_time = self.age
        self.artifacts.append(artifact)
        self._log(logging.DEBUG, "ARTIFACTS appended: '{}', length={}"
                  .format(artifact, len(self.artifacts)))

    def add_artifacts(self, artifacts):
        """Add artifacts to :attr:`artifacts`.

        :param artifacts: list of :py:class:`~creamas.core.artifact.Artifact` objects
        """
        for artifact in artifacts:
            self.add_artifact(artifact)

    async def get_artifacts(self, agent=None):
        """Return artifacts published to the environment.

        :param agent: If not ``None``, then returns only artifacts created by the agent.

        :returns: All artifacts published (by the agent).
        :rtype: list

        If environment has a :attr:`manager` agent, e.g. it is a slave environment in
        :class:`~creamas.mp.MultiEnvironment`, then the manager's :meth:`~creamas.mp.EnvManager.get_artifacts` is called.
        """
        # TODO: Figure better way for this
        if hasattr(self, 'manager') and self.manager is not None:
            artifacts = await self.manager.get_artifacts()
        else:
            artifacts = self.artifacts
        if agent is not None:
            artifacts = [a for a in artifacts if agent.name == a.creator]
        return artifacts

    def _log(self, level, msg):
        if self.logger is not None:
            self.logger.log(level, msg)

    def save_info(self, folder, *args, **kwargs):
        """Save information accumulated during the environments lifetime.

        Called from :py:meth:`~creamas.core.Environment.destroy`. Override in subclass.

        :param str folder: root folder to save information
        """
        pass

    def destroy(self, folder=None, as_coro=False):
        """Close the environment.

        .. deprecated:: 0.4.0
            Use :func:`close` instead

        """
        DeprecationWarning("{0}.destroy is deprecated, use {0}.close instead.".format(str(self.__class__.__name__)))
        return self.close(folder=folder, as_coro=as_coro)

    def close(self, folder=None, as_coro=False):
        """Close the environment.

        Does the following:

        1. calls :py:meth:`~creamas.core.Environment.save_info`
        2. for each agent: calls :py:meth:`close`
        3. Shuts down its RPC-service.
        """
        async def _close(folder):
            ret = self.save_info(folder)
            for a in self.get_agents(addr=False):
                a.close(folder=folder)
            await self.shutdown(as_coro=True)
            return ret

        return run_or_coro(_close(folder), as_coro)

    def __str__(self):
        return self.__repr__()

    def __repr__(self):
        return "{}({})".format(self.__class__.__name__, self.name) class Simulation:
    """A base class for iterative simulations.

    In each step the simulation calls :py:meth:`~creamas.core.agent.CreativeAgent.act` for each agent in the simulation.

    Functions :py:meth:`~creamas.core.simulation.Simulation.step`,
    :py:meth:`~creamas.core.simulation.Simulation.steps`, :py:meth:`~creamas.core.simulation.Simulation.async_step`
    and :py:meth:`~creamas.core.simulation.Simulation.async_steps` are used to advance the simulation.
    """
    @classmethod
    def create(cls, agent_cls=None, n_agents=10, agent_kwargs={}, env_cls=Environment, env_kwargs={}, callback=None,
               conns=0, log_folder=None):
        """A convenience function to create simple simulations.

        Method first creates an environment, then instantiates agents into it with given arguments, and finally creates
        simulation for the environment.

        :param agent_cls:
            class for agents, or list of classes. If list, then **n_agents** and **agent_kwargs** are expected to be
            lists also.

        :param n_agents:
            amount of agents for simulation, or list of amounts

        :param agent_kwargs:
            keyword arguments passed to agents at creation time, or list of keyword arguments.

        :param env_cls:
            environment class for simulation

        :type env_cls:
            :py:class:`~creamas.core.environment.Environment`

        :param dict env_kwargs:
            keyword arguments passed to environment at creation time

        :param callable callback:
            optional callable to call after each simulation step

        :param conns:
            Create **conns** amount of initial (random) connections for agents in the simulation environment.

        :param str log_folder:
            folder for possible logging. This overwrites *log_folder* keyword argument from **agent_kwargs** and
            **env_kwargs**.
        """
        if not issubclass(env_cls, Environment):
            raise TypeError("Environment class must be derived from ({}"
                            .format(Environment.__class__.__name__))
        if callback is not None and not hasattr(callback, '__call__'):
            raise TypeError("Callback must be callable.")

        if hasattr(agent_cls, '__iter__'):
            for e in agent_cls:
                if not issubclass(e, CreativeAgent):
                    raise TypeError("All agent classes must be derived from {}"
                                    .format(CreativeAgent.__class__.__name__))
        else:
            if not issubclass(agent_cls, CreativeAgent):
                raise TypeError("Agent class must be derived from {}"
                                .format(CreativeAgent.__class__.__name__))

        env = env_cls.create(**env_kwargs)

        agents = []
        if hasattr(agent_cls, '__iter__'):
            for i in range(len(n_agents)):
                agent_kwargs[i]['environment'] = env
                agent_kwargs[i]['log_folder'] = log_folder
                agents = agents + [agent_cls[i](**agent_kwargs[i]) for _ in
                                   range(n_agents[i])]
        else:
            agent_kwargs['environment'] = env
            agent_kwargs['log_folder'] = log_folder
            agents = [agent_cls(**agent_kwargs) for _ in range(n_agents)]

        if conns > 0:
            env.create_random_connections(n=conns)

        return Simulation(env, callback, log_folder)

    def __init__(self, env, callback=None, log_folder=None):
        """Create a simulation for an existing environment.

        :param env: An environment.
        :type env:
            :class:`~creamas.core.environment.Environment`,
            :class:`~creamas.mp.MultiEnvironment` or
            :class:`~creamas.ds.DistributedEnvironment`
        :param callable callback: function to call after each simulation step
        :param str log_folder: folder to log simulation information
        """
        self._env = env
        self._callback = callback
        self._cur_step = 0
        self._order = 'alphabetical'
        self._name = 'sim'
        self._start_time = time.monotonic()
        self._step_start_time = None
        self._step_processing_time = 0.0
        self._processing_time = 0.0
        self._end_time = None

        # List of agents that have not been triggered for current step.
        self._agents_to_act = []

        if type(log_folder) is str:
            self.logger = ObjectLogger(self, log_folder, add_name=False,
                                       init=True)
        else:
            self.logger = None

    @property
    def name(self):
        """Name of the simulation as a string.

        """
        return self._name

    @property
    def env(self):
        """Environment for the simulation.
        """
        return self._env

    @property
    def cur_step(self):
        """The simulation's current step as an integer.
        """
        return self._cur_step

    @property
    def callback(self):
        """Callable to be called after each simulation step for any extra bookkeeping, etc.. Callback should accept one
        parameter: *cur_step* that is the simulation's current step.
        """
        return self._callback

    @property
    def order(self):
        """Order in which agents are run. Order is not enforced for asynchronous executions.

        Possible values:

        * alphabetical: agents are sorted by name
        * random: agents are shuffled

        Changing the order while iteration is unfinished will take place in the next iteration.
        """
        return self._order

    @order.setter
    def order(self, order):
        assert order in ['alphabetical', 'random']
        self._order = order

    def _get_order_agents(self):
        agents = self.env.get_agents(addr=True)
        if self.order == 'alphabetical':
            return sorted(agents)
        shuffle(agents)
        return agents

    def _init_step(self):
        """Initialize next step of simulation to be run.
        """
        self._cur_step += 1
        self.env.age = self.cur_step
        self._log(logging.INFO, "")
        self._log(logging.INFO, "\t***** Step {:0>10} *****". format(self.cur_step))
        self._log(logging.INFO, "")
        self._agents_to_act = self._get_order_agents()
        self._step_processing_time = 0.0
        self._step_start_time = time.monotonic()

    def _finalize_step(self):
        """Finalize simulation step after all agents have acted for the current
        step.
        """
        t = time.time()
        if self._callback is not None:
            self._callback(self.cur_step)
        t2 = time.monotonic()
        self._step_processing_time += t2 - t
        self._log(logging.INFO, "Step {} run in: {:.3f}s ({:.3f}s of "
                  "actual processing time used)"
                  .format(self.cur_step, self._step_processing_time,
                          t2 - self._step_start_time))
        self._processing_time += self._step_processing_time

    def finish_step(self):
        """Progress simulation to the end of the current step.

        .. deprecated:: 0.4.0
            Use :func:`step` instead.
        """
        rets = []
        while len(self._agents_to_act) > 0:
            ret = self.next()
            rets.append(ret)
        return rets

    def step(self):
        """Progress simulation by a single step.
        """
        assert len(self._agents_to_act) == 0
        t = time.monotonic()
        rets = []

        self._init_step()

        while len(self._agents_to_act) > 0:
            addr = self._agents_to_act.pop(0)
            ret = util.run(self.env.trigger_act(addr=addr))
            rets.append(ret)

        self._finalize_step()
        self._step_processing_time = time.monotonic() - t
        return rets

    def steps(self, n):
        """Progress simulation with given amount of steps.

        Can not be called when some of the agents have not acted for the current step.

        :param int n: amount of steps to run
        """
        assert len(self._agents_to_act) == 0
        rets = []
        for _ in range(n):
            ret = self.step()
            rets.append(ret)

        return rets

    def async_step(self):
        """Progress simulation by running all agents asynchronously once.
        """
        assert len(self._agents_to_act) == 0
        self._init_step()
        t = time.time()
        ret = util.run(self.env.trigger_all())
        self._agents_to_act = []
        self._step_processing_time = time.monotonic() - t
        self._finalize_step()
        return ret

    def async_steps(self, n):
        """Progress simulation by running all agents *n* times asynchronously.
        """
        assert len(self._agents_to_act) == 0
        rets = []
        for _ in range(n):
            ret = self.async_step()
            rets.append(ret)

        return rets

    def _log(self, level, msg):
        if self.logger is not None:
            self.logger.log(level, msg)

    def end(self, folder=None):
        """Close the simulation and the current simulation environment.

        .. deprecated:: 0.4.0
            Use func:`close` instead.
        """
        return self.close(folder=folder)

    def close(self, folder=None):
        """Close the simulation and the current simulation environment.
        """
        ret = self.env.close(folder=folder)
        self._end_time = time.time()
        self._log(logging.DEBUG, "{} step simulation completed in {:.3f}s (actual processing time {:.3f}s)."
                  .format(self.cur_step, self._end_time - self._start_time, self._processing_time))
        return ret class MultiEnvironment():
    """Environment for utilizing multiple processes (and cores) on a single
    machine. :class:`MultiEnvironment` is not a subclass of
    :class:`creamas.core.environment.Environment`.

    :py:class:`MultiEnvironment` has a master environment, typically
    containing only a single manager, and a set of slave environments each
    having their own manager and (once spawned) the actual agents.

    Order of usage is::

        import aiomas

        from creamas Environment
        from creamas.mp import EnvManager, MultiEnvironment
        from creamas.util import run

        # Create the multi-environment and the environment used to connect to
        # slave environments
        addr = ('localhost', 5555)
        env_cls = Environment
        env_kwargs = {'codec': aiomas.MsgPack}
        menv = MultiEnvironment(addr, env_cls, **env_kwargs)

        # Define slave environments and their arguments
        slave_addrs = [('localhost', 5556), ('localhost', 5557)]
        slave_env_cls = Environment
        slave_mgr_cls = EnvManager
        n_slaves = 2
        slave_kwargs = [{'codec': aiomas.MsgPack} for _ in range(n_slaves)]

        # Spawn the actual slave environments
        menv.spawn_slaves(slave_addrs, slave_env_cls,
                          slave_mgr_cls, slave_kwargs)

        # Wait that all the slaves are ready, if you need to do some other
        # preparation before environments' return True for is_ready, then
        # change check_ready=False
        run(menv.wait_slaves(10, check_ready=True))

        # Do any additional preparations here, like spawning the agents to
        # slave environments.

        # Trigger all agents to act
        run(menv.trigger_all())

        # Destroy the environment to free the resources
        menv.destroy(as_coro=False)
    """
    def __init__(self, addr, env_cls, mgr_cls=None, name=None,
                 logger=None, **env_kwargs):
        """
        :param addr:
            ``(HOST, PORT)`` address for the master environment.

        :param env_cls:
            Class for the master environment, used to make connections to the
            slave environments. Must be a subclass of
            :py:class:`~creamas.core.environment.Environment`.

        :param mgr_cls:
            Class for the multi-environment's manager.

        :param str name: Name of the environment. Will be shown in logs.

        :param logger:
            Optional. Logger for the master environment.
        """
        self._addr = addr
        self._env = env_cls.create(addr, **env_kwargs)

        if mgr_cls is not None:
            self._manager = mgr_cls(self._env)
            self._manager.menv = self
        else:
            self._manager = None

        self._age = 0
        self._artifacts = []
        self._candidates = []
        self._manager_addrs = []

        if type(name) is str:
            self._name = name
        else:
            self._name = "{}:{}".format(addr[0], addr[1])

        self._logger = logger
        self._pool = None
        self._r = None

    def __str__(self):
        return self.__repr__()

    def __repr__(self):
        return "{}({})".format(self.__class__.__name__, self.name)

    @property
    def name(self):
        """The name of the environment.
        """
        return self._name

    @property
    def env(self):
        """The environment hosting the manager of this multi-environment.

        This environment is also used without the manager to connect to the
        slave environment managers and communicate with them.
        """
        return self._env

    def get_agents(self, addr=True, agent_cls=None, as_coro=False):
        """Get agents from the slave environments.

        :param bool addr:
            If ``True``, returns only addresses of the agents, otherwise
            returns a :class:`Proxy` object for each agent.

        :param agent_cls:
            If specified, returns only agents that are members of that
            particular class.

        :param bool as_coro:
            If ``True``, returns a coroutine, otherwise runs the method in
            an event loop.

        :returns:
            A coroutine or list of :class:`Proxy` objects or addresses as
            specified by the input parameters.

        Slave environment managers are excluded from the returned list by
        default. Essentially, this method calls each slave environment
        manager's :meth:`~creamas.mp.EnvManager.get_agents` asynchronously.

        .. tip::

            Calling each slave environment's manager might be costly in some
            situations. Therefore, it is advisable to store the returned agent
            list if the agent sets in the slave environments are not bound to
            change.
        """
        async def slave_task(mgr_addr, addr=True, agent_cls=None):
            r_manager = await self.env.connect(mgr_addr, timeout=TIMEOUT)
            return await r_manager.get_agents(addr=addr, agent_cls=agent_cls)

        tasks = create_tasks(slave_task, self.addrs, addr, agent_cls)
        return run_or_coro(tasks, as_coro)

    @property
    def addrs(self):
        """Addresses of the slave environment managers.
        """
        return self._manager_addrs

    @property
    def manager(self):
        """This multi-environment's master manager.
        """
        return self._manager

    @property
    def logger(self):
        """Logger for the environment.

        Logger should have at least :meth:`log` method which takes two
        arguments: a log level and a message.
        """
        return self._logger

    @property
    def artifacts(self):
        """Published artifacts for all agents.
        """
        return self._artifacts

    async def connect(self, *args, **kwargs):
        """A shortcut to environment's :meth:`connect`.
        """
        return await self.env.connect(*args, **kwargs)

    def check_ready(self):
        """Check if this multi-environment itself is ready.

        Override in subclass if it needs any additional (asynchronous)
        initialization other than spawning its slave environments.

        :rtype: bool
        :returns: This basic implementation returns always True.
        """
        return True

    async def is_ready(self):
        """Check if the multi-environment has been fully initialized.

        This calls each slave environment managers' :py:meth:`is_ready` and
        checks if the multi-environment itself is ready by calling
        :py:meth:`~creamas.mp.MultiEnvironment.check_ready`.

        .. seealso::

            :py:meth:`~creamas.core.environment.Environment.is_ready`
        """
        async def slave_task(addr, timeout):
            try:
                r_manager = await self.env.connect(addr, timeout=timeout)
                ready = await r_manager.is_ready()
                if not ready:
                    return False
            except:
                return False
            return True

        if not self.env.is_ready():
            return False
        if not self.check_ready():
            return False
        rets = await create_tasks(slave_task, self.addrs, 0.5)
        if not all(rets):
            return False
        return True

    async def spawn_slaves(self, slave_addrs, slave_env_cls, slave_mgr_cls,
                           slave_kwargs=None):
        """Spawn slave environments.

        :param slave_addrs:
            List of (HOST, PORT) addresses for the slave-environments.

        :param slave_env_cls: Class for the slave environments.

        :param slave_kwargs:
            If not None, must be a list of the same size as *addrs*. Each item
            in the list containing parameter values for one slave environment.

        :param slave_mgr_cls:
            Class of the slave environment managers.
        """
        pool, r = spawn_containers(slave_addrs, env_cls=slave_env_cls,
                                   env_params=slave_kwargs,
                                   mgr_cls=slave_mgr_cls)
        self._pool = pool
        self._r = r
        self._manager_addrs = ["{}{}".format(_get_base_url(a), 0) for
                               a in slave_addrs]

    async def wait_slaves(self, timeout, check_ready=False):
        """Wait until all slaves are online (their managers accept connections)
        or timeout expires.

        :param int timeout:
            Timeout (in seconds) after which the method will return even though
            all the slaves are not online yet.

        :param bool check_ready:
            If ``True`` also checks if all slave environment's are ready.
            A slave environment is assumed to be ready when its manager's
            :meth:`is_ready`-method returns ``True``.

        .. seealso::

            :meth:`creamas.core.environment.Environment.is_ready`,
            :meth:`creamas.mp.EnvManager.is_ready`,
            :meth:`creamas.mp.MultiEnvManager.is_ready`

        """
        status = 'ready' if check_ready else 'online'
        self._log(logging.DEBUG,
                  "Waiting for slaves to become {}...".format(status))
        t = time.monotonic()
        online = []
        while len(online) < len(self.addrs):
            for addr in self.addrs:
                if time.monotonic() - t > timeout:
                    self._log(logging.DEBUG, "Timeout while waiting for the "
                              "slaves to become {}.".format(status))
                    return False
                if addr not in online:
                    try:
                        r_manager = await self.env.connect(addr, timeout)
                        ready = True
                        if check_ready:
                            ready = await r_manager.is_ready()
                        if ready:
                            online.append(addr)
                            self._log(logging.DEBUG, "Slave {}/{} {}: {}"
                                      .format(len(online),
                                              len(self.addrs),
                                              status,
                                              addr))
                    except:
                        pass
            await asyncio.sleep(0.5)
        self._log(logging.DEBUG, "All slaves {} in {} seconds!"
                  .format(status, time.monotonic() - t))
        return True

    def _get_log_folders(self, log_folder, addrs):
        if type(log_folder) is str:
            import os
            folders = [os.path.join(log_folder, '_{}'.format(i)) for i in
                       range(len(addrs))]
            return folders
        else:
            folders = [None for _ in range(len(addrs))]
            return folders

    async def set_host_manager(self, addr, timeout=TIMEOUT):
        """Set this multi-environment's manager as the host manager for
        a manager agent in *addr*
        """
        r_manager = await self.env.connect(addr, timeout=timeout)
        return await r_manager.set_host_manager(self.manager.addr)

    async def set_host_managers(self, timeout=5):
        """Set the master environment's manager as host manager for the slave
        environment managers.

        :param int timeout: Timeout for connecting to the slave managers.

        This enables the slave environment managers to communicate back to the
        master environment. The master environment manager,
        :attr:`~creamas.mp.MultiEnvironment.manager`, must be an instance
        of :class:`~creamas.mp.MultiEnvManager` or its subclass if this method
        is called.
        """
        return await create_tasks(self.set_host_manager, self.addrs, timeout)

    async def trigger_act(self, addr):
        """Trigger agent in :attr:`addr` to act.

        This method is quite inefficient if used repeatedly for a large number
        of agents.

        .. seealso::

            :py:meth:`creamas.mp.MultiEnvironment.trigger_all`
        """
        r_agent = await self.env.connect(addr, timeout=TIMEOUT)
        return await r_agent.act()

    async def trigger_all(self, *args, **kwargs):
        """Trigger all agents in all the slave environments to :meth:`act`
        asynchronously.

        Given arguments and keyword arguments are passed down to each agent's
        :meth:`~creamas.core.agent.CreativeAgent.act`.

        .. note::

            By design, the manager agents in each slave environment, i.e.
            :attr:`manager`, are excluded from acting.
        """
        async def slave_task(addr, *args, **kwargs):
            r_manager = await self.env.connect(addr, timeout=TIMEOUT)
            return await r_manager.trigger_all(*args, **kwargs)

        return await create_tasks(slave_task, self.addrs, *args, **kwargs)

    async def _get_smallest_env(self):
        """Get address of the slave environment manager with the smallest
        number of agents.
        """
        async def slave_task(mgr_addr):
            r_manager = await self.env.connect(mgr_addr, timeout=TIMEOUT)
            ret = await r_manager.get_agents(addr=True)
            return mgr_addr, len(ret)

        sizes = await create_tasks(slave_task, self.addrs, flatten=False)
        return sorted(sizes, key=lambda x: x[1])[0][0]

    async def spawn(self, agent_cls, *args, addr=None, **kwargs):
        """Spawn a new agent in a slave environment.

        :param str agent_cls:
            ``qualname`` of the agent class.
            That is, the name should be in the form ``pkg.mod:cls``, e.g.
            ``creamas.core.agent:CreativeAgent``.
        :param str addr:
            Optional. Address for the slave enviroment's manager.
            If ``None``, spawns the agent in the slave environment
            with currently smallest number of agents.

        :returns: :class:`aiomas.rpc.Proxy` and address for the created agent.

        The ``*args`` and ``**kwargs`` are passed down to the agent's
        ``__init__``.

        .. tip::

            Use :meth:`~creamas.mp.MultiEnvironment.spawn_n` to spawn large
            number of agents with identical initialization parameters.
        """
        if addr is None:
            addr = await self._get_smallest_env()
        r_manager = await self.env.connect(addr)
        return await r_manager.spawn(agent_cls, *args, **kwargs)

    async def spawn_n(self, agent_cls, n, *args, addr=None, **kwargs):
        """Same as :meth:`~creamas.mp.MultiEnvironment.spawn`, but allows
        spawning multiple agents with the same initialization parameters
        simultaneously into **one** slave environment.

        :param str agent_cls:
            ``qualname`` of the agent class. That is, the name should be in the
            form of ``pkg.mod:cls``, e.g. ``creamas.core.agent:CreativeAgent``.
        :param int n: Number of agents to spawn
        :param str addr:
            Optional. Address for the slave enviroment's manager.
            If ``None``, spawns the agents in the slave environment
            with currently smallest number of agents.

        :returns:
            A list of (:class:`aiomas.rpc.Proxy`, address)-tuples for the
            spawned agents.

        The ``*args`` and ``**kwargs`` are passed down to each agent's
        ``__init__``.
        """
        if addr is None:
            addr = await self._get_smallest_env()
        r_manager = await self.env.connect(addr)
        return await r_manager.spawn_n(agent_cls, n, *args, **kwargs)

    def create_connections(self, connection_map, as_coro=False):
        """Create agent connections from the given connection map.

        :param dict connection_map:
            A map of connections to be created. Dictionary where keys are
            agent addresses and values are lists of (addr, data)-tuples
            suitable for
            :meth:`~creamas.core.agent.CreativeAgent.add_connections`.

        :param bool as_coro:
            If ``True`` returns a coroutine, otherwise runs the asynchronous
            calls to the slave environment managers in the event loop.

        Only the connections for the agents that are in the slave environments
        are created.
        """
        async def slave_task(addr, connection_map):
            r_manager = await self.env.connect(addr)
            return await r_manager.create_connections(connection_map)

        tasks = create_tasks(slave_task, self.addrs, connection_map)
        return run_or_coro(tasks, as_coro)

    def get_connections(self, data=True, as_coro=False):
        """Return connections from all the agents in the slave environments.

        :param bool data:
            If ``True``, returns also the data stored for each connection.

        :param bool as_coro:
            If ``True`` returns a coroutine, otherwise runs the asynchronous
            calls to the slave environment managers in the event loop.

        .. seealso::

            :meth:`creamas.core.environment.Environment.get_connections`
        """
        async def slave_task(addr, data):
            r_manager = await self.env.connect(addr)
            return await r_manager.get_connections(data)

        tasks = create_tasks(slave_task, self.addrs, data)
        return run_or_coro(tasks, as_coro)

    def get_slave_managers(self):
        """Get addresses of all slave environment managers.
        """
        return self.addrs

    def add_artifact(self, artifact):
        """Add an artifact to the environment.

        :param object artifact: Artifact to be added.
        """
        artifact.env_time = self.age
        self.artifacts.append(artifact)
        self._log(logging.DEBUG, "ARTIFACTS appended: '{}', length={}"
                  .format(artifact, len(self.artifacts)))

    def add_artifacts(self, artifacts):
        """Add artifacts to :attr:`artifacts`.

        :param artifacts:
            list of :py:class:`~creamas.core.artifact.Artifact` objects
        """
        for artifact in artifacts:
            self.add_artifact(artifact)

    def get_artifacts(self, agent_name=None):
        """Get all artifacts or all artifacts published by a specific agent.

        :param str agent_name:
            Optional. Name of the agent which artifacts are returned.

        :returns: All artifacts or all artifacts published by the agent.
        :rtype: list
        """
        if agent_name is not None:
            return [a for a in self.artifacts if agent_name == a.creator]
        return self.artifacts

    def _log(self, level, msg):
        if self.logger is not None:
            self.logger.log(level, msg)

    def save_info(self, folder, *args, **kwargs):
        """Save information accumulated during the environment's lifetime.

        Called from :py:meth:`~creamas.mp.MultiEnvironment.destroy`. Override
        in subclass.

        :param str folder: root folder to save information
        """
        pass

    async def stop_slaves(self, timeout=1):
        """Stop all the slaves by sending a stop-message to their managers.

        :param int timeout:
            Timeout for connecting to each manager. If a connection can not
            be made before the timeout expires, the resulting error for that
            particular manager is logged, but the stopping of other managers
            is not halted.
        """
        for addr in self.addrs:
            try:
                r_manager = await self.env.connect(addr, timeout=timeout)
                await r_manager.stop()
            except:
                self._log(logging.WARNING, "Could not stop {}".format(addr))

    def destroy(self, folder=None, as_coro=False):
        """Close the multiprocessing environment and its slave environments.

        .. deprecated:: 0.4.0
            Use :func:`close` instead
        """
        DeprecationWarning("{0}.destroy is deprecated, use {0}.close instead.".format(str(self.__class__.__name__)))
        return self.close(folder=folder, as_coro=as_coro)

    def close(self, folder=None, as_coro=False):
        """Close the multiprocessing environment and its slave environments.
        """
        async def _close(folder):
            ret = self.save_info(folder)
            await self.stop_slaves()
            # Terminate and join the process pool when we are destroyed.
            # Do not wait for unfinished processed with pool.close(),
            # the slaves should be anyway already stopped.
            if self._pool is not None:
                self._pool.terminate()
                self._pool.join()
            await self._env.shutdown(as_coro=True)
            return ret

        return run_or_coro(_close(folder), as_coro) class MultiEnvManager(Manager):
    """A manager for :class:`~creamas.mp.MultiEnvironment`, which is a subclass of :class:`aiomas.subproc.Manager`.

    A Manager can spawn other agents into its slave environments, and can
    execute other tasks relevant to the whole environment. The manager should
    always be the first (and usually only) agent created for the
    multi-environment's managing environment. The actual simulation agents
    should be created to the slave environments, typically using
    multi-environment's or its manager's functionality.

    .. note::
        You should not need to create managers directly, instead pass the
        desired manager class to an instance of
        :class:`~creamas.mp.MultiEnvironment` at its initialization time.
    """
    def __init__(self, environment):
        super().__init__(environment)

    @property
    def env(self):
        return self.container

    @expose
    def handle(self, msg):
        """Handle message from a slave manager.

        **This is a dummy method which should be overridden in a subclass.**
        """
        pass

    @expose
    async def spawn(self, agent_cls, *args, addr=None, **kwargs):
        """Spawn an agent to the environment.

        This is a managing function for
        :meth:`~creamas.mp.MultiEnvironment.spawn`.

        .. note::

            Since :class:`aiomas.rpc.Proxy` objects do not seem to handle
            (re)serialization, only the address of the spawned agent is
            returned.
        """
        _, addr = await self.menv.spawn(agent_cls, *args, addr=addr, **kwargs)
        return addr

    @expose
    async def spawn_n(self, agent_cls, n, *args, addr=None, **kwargs):
        """Same as :meth:`~creamas.mp.MultiEnvManager.spawn`, but spawn
        :attr:`n` agents with same initialization parameters.

        This is a managing function for
        :meth:`~creamas.mp.MultiEnvironment.spawn_n`.

        .. note::

            Since :class:`aiomas.rpc.Proxy` objects do not seem to handle
            (re)serialization, only the addresses of the spawned agents are
            returned.
        """
        ret = await self.menv.spawn_n(agent_cls, n, *args, addr=addr, **kwargs)
        return [r[1] for r in ret]

    @expose
    async def get_agents(self, addr=True, agent_cls=None):
        """Get addresses of all agents in all the slave environments.

        This is a managing function for
        :meth:`~creamas.mp.MultiEnvironment.get_agents`.

        .. note::

            Since :class:`aiomas.rpc.Proxy` objects do not seem to handle
            (re)serialization, ``addr`` and ``agent_cls`` parameters are
            omitted from the call to underlying multi-environment's
            :meth:`get_agents`.

            If :class:`aiomas.rpc.Proxy` objects from all the agents are
            needed, call each slave environment manager's :meth:`get_agents`
            directly.
        """
        return await self.menv.get_agents(addr=True, agent_cls=None,
                                          as_coro=True)

    @expose
    async def create_connections(self, connection_map):
        """Create connections for agents in the multi-environment.

        This is a managing function for
        :meth:`~creamas.mp.MultiEnvironment.create_connections`.
        """
        return await self.menv.create_connections(connection_map, as_coro=True)

    @expose
    async def get_connections(self, data=True):
        """Return connections for all the agents in the slave environments.

        This is a managing function for
        :meth:`~creamas.mp.MultiEnvironment.get_connections`.
        """
        return await self.menv.get_connections(data=data, as_coro=True)

    @expose
    def close(self, folder=None):
        """Implemented for consistency. This basic implementation does nothing.
        """
        pass

    @expose
    async def set_as_host_manager(self, addr, timeout=5):
        """Set the this manager as a host manager for the manager in *addr*.

        This is a managing function for
        :py:meth:`~creamas.mp.MultiEnvironment.set_host_manager`.
        """
        return await self.menv.set_host_manager(addr, timeout=timeout)

    @expose
    async def trigger_all(self, *args, **kwargs):
        """Trigger all agents in the managed multi-environment to act.

        This is a managing function for
        :py:meth:`~creamas.mp.MultiEnvironment.trigger_all`.
        """
        return await self.menv.trigger_all(*args, **kwargs)

    @expose
    async def is_ready(self):
        """A managing function for
        :py:meth:`~creamas.mp.MultiEnvironment.is_ready`.
        """
        return await self.menv.is_ready()

    @expose
    async def get_artifacts(self):
        """Get all the artifacts from the multi-environment.
        """
        return self.menv.artifacts

    @expose
    async def get_slave_managers(self):
        """Get addresses of the slave environment managers in this
        multi-environment.
        """
        return self.menv.get_slave_managers() def gaus_pdf(x, mean, std):
    """Gaussian distribution's probability density function.

    See, e.g. `Wikipedia page for normal distribution <https://en.wikipedia.org/wiki/Normal_distribution>`_.

    :param x: point in x-axis
    :type x: float or numpy.ndarray
    :param float mean: mean or expectation
    :param float str: standard deviation
    :returns: pdf(s) in point **x**
    :rtype: float or numpy.ndarray
    """
    return exp(-((x - mean) / std)**2 / 2) / sqrt(2 * pi) / std class VoteAgent(CreativeAgent):
    """An agent with voting behavior.

    Implements three functions needed for voting:

    * :meth:`~creamas.vote.VoteAgent.validate`: Validates a set of candidates
      returning only the validated candidates.
    * :meth:`~creamas.vote.VoteAgent.vote`: Votes from a set of candidates,
      i.e. orders them by preference. Returns the ordered list and preferences.
      Basic implementation orders the candidates using the agent's
      :meth:`evaluate` function.
    * :meth:`~creamas.vote.VoteAgent.add_candidate`: Add candidate artifact to
      the agent's environment's list of current candidates.
    """

    @expose
    def validate(self, candidates):
        """Validate a list of candidate artifacts.

        Candidate validation should prune unwanted artifacts from the overall
        candidate set. Agent can use its own reasoning to validate the
        given candidates. The method should return a subset of the given
        candidates list containing the validated artifacts (i.e. the
        artifacts that are not pruned).

        .. note::
            This basic implementation returns the given candidate list as is.
            Override this function in the subclass for the appropriate
            validation procedure.

        :param candidates: A list of candidate artifacts
        :returns: The validated artifacts, a subset of given candidates
        """
        return candidates

    @expose
    def vote(self, candidates):
        """Rank artifact candidates.

        The voting is needed for the agents living in societies using
        social decision making. The function should return a sorted list
        of (candidate, evaluation)-tuples. Depending on the social choice
        function used, the evaluation might be omitted from the actual decision
        making, or only a number of (the highest ranking) candidates may be
        used.

        This basic implementation ranks candidates based on
        :meth:`~creamas.core.agent.CreativeAgent.evaluate`.

        :param candidates:
            list of :py:class:`~creamas.core.artifact.Artifact` objects to be
            ranked

        :returns:
            Ordered list of (candidate, evaluation)-tuples
        """
        ranks = [(c, self.evaluate(c)[0]) for c in candidates]
        ranks.sort(key=operator.itemgetter(1), reverse=True)
        return ranks

    def add_candidate(self, artifact):
        """Add artifact to the environment's current list of candidates.
        """
        self.env.add_candidate(artifact) class VoteManager(EnvManager):
    """Manager agent for voting environments.

    The class is designed be used in conjunction with
    :class:`~creamas.vote.VoteEnvironment` in multiprocessing and distributed
    settings.
    """
    @expose
    async def get_candidates(self):
        """Get current candidates from the managed environment.
        """
        return self.env.candidates

    @expose
    async def validate_candidates(self, candidates):
        """Validate the candidates with the agents in the managed environment.

        This is a managing function for
        :meth:`~creamas.vote.VoteEnvironment.validate_candidates`.
        """
        return self.env.validate_candidates(candidates)

    @expose
    def clear_candidates(self):
        """Clear candidates in the managed environment.

        This is a managing function for
        :py:meth:`~creamas.environment.Environment.clear_candidates`.
        """
        self.env.clear_candidates()

    @expose
    def get_votes(self, candidates):
        self.env._candidates = candidates
        votes = self.env.gather_votes()
        return votes

    @expose
    async def gather_votes(self, candidates):
        """Gather votes for the given candidates from the agents in the
        managed environment.

        This is a managing function for
        :py:meth:`~creamas.environment.Environment.gather_votes`.
        """
        return self.env.gather_votes(candidates) class VoteEnvironment(Environment):
    """An environment implementing functionality needed for voting.

    Voting depends largely on a list of :attr:`candidate` artifacts, which are
    passed down to agents at the time of voting. Candidate artifacts can also
    be validated by the agents (only returning artifacts that are deemed
    appropriate, or good enough, by all agents in the environment).
    """
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._candidates = []

    @property
    def candidates(self):
        """Current artifact candidates, subject to e.g. agents voting to
        determine which candidate(s) are added to :attr:`~artifacts`.
        """
        return self._candidates

    def clear_candidates(self):
        """Remove current candidate artifacts from the environment.
        """
        self._candidates = []

    def add_candidate(self, artifact):
        """Add candidate artifact to the list of current candidates.
        """
        self.candidates.append(artifact)
        self._log(logging.DEBUG, "CANDIDATES appended:'{}'"
                  .format(artifact))

    def validate_candidates(self, candidates):
        """Validate the candidate artifacts with the agents in the environment.

        In larger societies this method might be costly, as it calls each
        agents' :meth:`validate`.

        :returns:
            A list of candidates that are validated by all agents in the
            environment.
        """
        valid_candidates = set(candidates)
        for a in self.get_agents(addr=False):
            vc = set(a.validate(candidates))
            valid_candidates = valid_candidates.intersection(vc)

        return list(valid_candidates)

    def gather_votes(self, candidates):
        """Gather votes for the given candidates from the agents in the
        environment.

        Returned votes are anonymous, i.e. they cannot be tracked to any
        individual agent afterwards.

        :returns:
            A list of votes. Each vote is a list of ``(artifact, preference)``
            -tuples sorted in a preference order of a single agent.
        """
        votes = []
        for a in self.get_agents(addr=False):
            vote = a.vote(candidates)
            votes.append(vote)
        return votes class VoteOrganizer:
    """A class which organizes voting behavior in an environment.

    The organizer can :meth:`~creamas.vote.VoteOrganizer.gather_candidates`
    from the environment, and then
    :meth:`~creamas.vote.VoteOrganizer.gather_votes` for the candidates.
    Optionally the organizer may
    :meth:`~creamas.vote.VoteOrganizer.validate_candidates` before gathering
    the votes. After the votes have been collected the organizer may
    :meth:`~creamas.vote.VoteOrganizer.compute_results` of the votes with a
    given voting method.

    The organizer also has :meth:`~creamas.vote.VoteOrganizer.gather_and_vote`
    to do all of the above in one go.
    """
    def __init__(self, environment, logger=None):
        self._env = environment
        self._candidates = []
        self._votes = []
        self.logger = logger
        self._single_env = self._determine_single_env(environment)
        self._managers = None if self._single_env else []

    @property
    def env(self):
        """The environment associated with this voting organizer.
        """
        return self._env

    @property
    def candidates(self):
        """Current list of candidates gathered from the environment.
        """
        return self._candidates

    @property
    def votes(self):
        """Current list of votes gathered from the environment.
        """
        return self._votes

    def _determine_single_env(self, env):
        if issubclass(env.__class__, VoteEnvironment):
            return True
        return False

    def get_managers(self):
        """Get managers for the slave environments.
        """
        if self._single_env:
            return None
        if not hasattr(self, '_managers'):
            self._managers = self.env.get_slave_managers()
        return self._managers

    def gather_votes(self):
        """Gather votes from all the underlying slave environments for the
        current list of candidates.

        The votes are stored in :attr:`votes`, overriding any previous votes.
        """
        async def slave_task(addr, candidates):
            r_manager = await self.env.connect(addr)
            return await r_manager.gather_votes(candidates)

        if len(self.candidates) == 0:
            self._log(logging.DEBUG, "Could not gather votes because there are no candidates!")
            self._votes = []
            return
        self._log(logging.DEBUG, "Gathering votes for {} candidates.".format(len(self.candidates)))

        if self._single_env:
            self._votes = self.env.gather_votes(self.candidates)
        else:
            managers = self.get_managers()
            tasks = create_tasks(slave_task, managers, self.candidates)
            self._votes = run(tasks)

    def gather_candidates(self):
        """Gather candidates from the slave environments.

        The candidates are stored in :attr:`candidates`, overriding any
        previous candidates.
        """
        async def slave_task(addr):
            r_manager = await self.env.connect(addr)
            return await r_manager.get_candidates()

        if self._single_env:
            self._candidates = self.env.candidates
        else:
            managers = self.get_managers()
            tasks = create_tasks(slave_task, managers)
            self._candidates = run(tasks)

    def clear_candidates(self, clear_env=True):
        """Clear the current candidates.

        :param bool clear_env:
            If ``True``, clears also environment's (or its underlying slave
            environments') candidates.
        """
        async def slave_task(addr):
            r_manager = await self.env.connect(addr)
            return await r_manager.clear_candidates()

        self._candidates = []
        if clear_env:
            if self._single_env:
                self.env.clear_candidates()
            else:
                managers = self.get_managers()
                run(create_tasks(slave_task, managers))

    def validate_candidates(self):
        """Validate current candidates.

        This method validates the current candidate list in all the agents
        in the environment (or underlying slave environments) and replaces
        the current :attr:`candidates` with the list of validated candidates.

        The artifact candidates must be hashable and have a :meth:`__eq__`
        implemented for validation to work on multi-environments and
        distributed environments.
        """
        async def slave_task(addr, candidates):
            r_manager = await self.env.connect(addr)
            return await r_manager.validate_candidates(candidates)

        self._log(logging.DEBUG, "Validating {} candidates"
                  .format(len(self.candidates)))

        candidates = self.candidates
        if self._single_env:
            self._candidates = self.env.validate_candidates(candidates)
        else:
            mgrs = self.get_managers()
            tasks = create_tasks(slave_task, mgrs, candidates, flatten=False)
            rets = run(tasks)
            valid_candidates = set(self.candidates)
            for r in rets:
                valid_candidates = valid_candidates.intersection(set(r))
            self._candidates = list(valid_candidates)

        self._log(logging.DEBUG, "{} candidates after validation"
                  .format(len(self.candidates)))

    def gather_and_vote(self, voting_method, validate=False, winners=1,
                        **kwargs):
        """Convenience function to gathering candidates and votes and
        performing voting using them.

        Additional ``**kwargs`` are passed down to voting method.

        :param voting_method:
            The voting method to use, see
            :meth:`~creamas.vote.VoteOrganizer.compute_results` for details.
        :param bool validate: Validate gathered candidates before voting.
        :param int winners: The number of vote winners

        :returns: Winner(s) of the vote.
        """
        self.gather_candidates()
        if validate:
            self.validate_candidates()
        self.gather_votes()
        r = self.compute_results(voting_method, self.votes, winners=winners, **kwargs)
        return r

    def compute_results(self, voting_method, votes=None, winners=1, **kwargs):
        """Compute voting results to decide the winner(s) from the
        :attr:`votes`.

        The votes should have been made for the current
        :attr:`~creamas.vote.VoteOrganizer.candidates`.

        :param voting_method:
            A function which computes the results from the votes. Should
            accept at least three parameters: candidates, votes and number of
            vote winners. The function should return at least a list of vote
            winners. See, e.g. :func:`~creamas.vote.vote_mean` or
            :func:`~creamas.vote.vote_best`. Additional ``**kwargs`` are passed
            down to the voting method.

        :param list votes:
            A list of votes by which the voting is performed. Each vote should
            have the same set of artifacts in them. If ``None`` the results
            are computed for the current list of
            :attr:`~creamas.vote.VoteOrganizer.votes`.

        :param int winners:
            The number of vote winners

        :returns:
            list of :py:class:`~creamas.core.artifact.Artifact` objects,
            the winning artifacts. Some voting methods may also return a score
            associated with each winning artifact.

        :rtype: list
        """
        if votes is None:
            votes = self.votes

        if len(votes) == 0:
            self._log(logging.DEBUG, "Could not compute results as there are "
                      "no votes!")
            return []

        self._log(logging.DEBUG, "Computing results from {} votes."
                  .format(len(votes)))
        return voting_method(self.candidates, votes, winners, **kwargs)

    def _log(self, level, msg):
        if self.logger is not None:
            self.logger.log(level, msg) def vote_mean(candidates, votes, n_winners):
    """Perform mean voting based on votes.

    Mean voting computes the mean preference for each of the artifact
    candidates from the votes and sorts the candidates in the mean preference
    order.

    Ties are resolved randomly.

    :param candidates: All candidates in the vote
    :param votes: Votes from the agents
    :param int n_winners: The number of vote winners
    """
    sums = {str(candidate): [] for candidate in candidates}
    for vote in votes:
        for v in vote:
            sums[str(v[0])].append(v[1])
    for s in sums:
        sums[s] = sum(sums[s]) / len(sums[s])
    ordering = list(sums.items())
    ordering.sort(key=operator.itemgetter(1), reverse=True)
    best = ordering[:min(n_winners, len(ordering))]
    d = []
    for e in best:
        for c in candidates:
            if str(c) == e[0]:
                d.append((c, e[1]))
    return d class ObjectLogger():
    '''Base logger for Creamas objects. *Not* a subclass of
    :class:`logging.Logger`.

    Generates one file for each attribute to be logged.
    '''
    def __init__(self, obj, folder, add_name=False, init=True,
                 log_level=logging.DEBUG):
        '''Create new logger instance for *obj* in *folder*. If *add_name*
        is ``True``, creates subfolder carrying :attr:`obj.name` to *folder*.
        If *init* is true, sets logger's level to *log_level* and adds basic
        *StreamHandler* to the logger.
        '''
        self._obj = obj
        self._folder = folder

        if add_name:
            import re
            a = re.split("[:/]", self._obj.name)
            fold = "_".join([i for i in a if len(i) > 0])
            obj_folder = os.path.join(self._folder, fold)
            if not os.path.exists(obj_folder):
                os.makedirs(obj_folder)
            self._folder = obj_folder

        self.logger = logging.getLogger("creamas.{}".format(self._obj.name))
        if len(self.logger.handlers) == 0:
            self.logger.addHandler(logging.NullHandler())
            if init:
                self.logger.setLevel(log_level)
                ch = logging.StreamHandler()
                ch.setLevel(log_level)
                self.logger.addHandler(ch)

        self.DEBUG = logging.DEBUG
        self.INFO = logging.INFO
        self.ERROR = logging.ERROR
        self.CRITICAL = logging.CRITICAL
        self.WARNING = logging.WARNING

    @property
    def obj(self):
        '''Object this logger belongs to. Object has to have **name**
        attribute.'''
        return self._obj

    @property
    def folder(self):
        '''Root logging folder for this logger.'''
        return self._folder

    def add_handler(self, handler):
        '''Add handler to the logger.
        '''
        self.logger.addHandler(handler)

    def get_file(self, attr_name):
        '''Return absolute path to logging file for obj's attribute.'''
        return os.path.abspath(os.path.join(self.folder, "{}.log"
                                            .format(attr_name)))

    def log_attr(self, level, attr_name):
        '''Log attribute to file and pass the message to underlying logger.

        :param int level: logging level
        :param str attr_name: attribute's name to be logged
        '''
        msg = self.write(attr_name)
        self.log(level, msg)

    def log(self, level, msg):
        self.logger.log(level, "{}: {}".format(self.obj.name, msg))
        sys.stdout.flush()

    def write(self, attr_name, prefix=None):
        '''Write attribute's value to a file.

        :param str attr_name:
            Attribute's name to be logged

        :param str prefix:
            Optional. Attribute's name that is prefixed to logging message,
            defaults to ``None``.

        :returns: message written to file
        :rtype: str
        '''
        if self._folder is None:
            return

        separator = "\t"
        attr = getattr(self.obj, attr_name)
        if hasattr(attr, '__iter__'):
            msg = separator.join([str(e) for e in attr])
        else:
            msg = str(attr)

        if prefix is not None:
            msg = "{}\t{}".format(getattr(self.obj, prefix), msg)

        path = self.get_file(attr_name)
        with open(path, 'a') as f:
            f.write("{}\n".format(msg))

        return msg def run(task=None, loop=None):
    """Run the event loop forever or until the task/future *task* is finished.

    :param task:
        Optional. Task or Future which is run until complete. If parameter is
        ``None`` runs the event loop forever.
    :param loop:
        Optional. Event loop to use. If the parameter is ``None`` uses
        asyncio's base event loop.

    .. note::
        This method has the same intent as :func:`aiomas.util.run`.
    """
    if loop is None:
        loop = asyncio.get_event_loop()
    if task is None:
        return loop.run_forever()
    else:
        return loop.run_until_complete(task) def create_tasks(task_coro, addrs, *args, flatten=True, **kwargs):
    """Create and schedule a set of asynchronous tasks.

    The function creates the tasks using a given list of agent addresses and
    wraps each of them in :func:`asyncio.ensure_future`. The ``*args`` and
    ``**kwargs`` are passed down to :func:`task_coro` when creating tasks for
    each address in :attr:`addrs`.

    Usage example for a method in a class derived from
    :class:`~creamas.mp.MultiEnvironment`::

        async def my_method(self, *args, **kwargs):
            async def task(addr, *args, **kwargs):
                r_manager = await self.env.connect(addr)
                return await r_manager.my_method(*args, **kwargs)

            return await util.create_tasks(task, self.addrs, *args, **kwargs)

    :param task_coro:
        Coroutine which is used for each address in :attr:`addrs`. The
        coroutine should accept an agent address as the first parameter.
    :param list addrs:
        A list of agent addresses used as the first parameters of
        :func:`task_coro`.
    :param bool flatten:
        If ``True`` the returned results are flattened into one list if the
        tasks return iterable objects. The parameter does nothing if all the
        results are not iterable.
    :returns:
        An awaitable coroutine which returns the results of tasks as a list or
        as a flattened list
    """
    tasks = []
    for agent_addr in addrs:
        task = asyncio.ensure_future(task_coro(agent_addr, *args, **kwargs))
        tasks.append(task)
    return wait_tasks(tasks, flatten)
import os
import sys
import time
import logging
import operator
import pickle
import aiomas
import numpy as np
    import asyncio
    import aiomas
from collections import Counter
from random import shuffle
from scipy import misc
from creamas import Artifact, Environment, Simulation
from creamas.mp import MultiEnvironment, MultiEnvManager
from creamas.math import gaus_pdf
from creamas.vote import VoteAgent,VoteManager, VoteEnvironment, VoteOrganizer, vote_mean
from creamas.logging import ObjectLogger
from creamas.util import run, create_tasks
from spiro import give_dots, give_dots_yield, spiro_image
        from matplotlib import pyplot as plt
        from matplotlib import pyplot as plt
        from matplotlib import pyplot as plt
        from matplotlib import pyplot as plt
        from matplotlib import pyplot as plt
    from matplotlib import pyplot as plt
    from serializers import get_spiro_ser
        """Plot distances of the generated spirographs w.r.t. the previously
        generated spirogaphs.
        """
        x = np.arange(len(distances))
        y = [mean_dist for i in x]
        fig, ax = plt.subplots()
        data_line = ax.plot(indeces, distances, label='Min Distance to previous',
                        marker='.', color='black', linestyle="")
        mean_line = ax.plot(indeces, y, label='Mean', linestyle='--', color='green')
        if len(distances) > 0:
            z = np.poly1d(np.polyfit(x,distances,2))
            f = [z(i) for i in x]
            mean_line = ax.plot(indeces, f, label='Fitted', linestyle='-', color='red')
        legend = ax.legend(loc='upper right', prop={'size':8})
        agent_vars = "{}_{}_{}{}_last={}_stmem=list{}_veto={}_sc={}_jump={}_sw={}_mr={}_maxN".format(
            self.name, self.age, self.env_learning_method, self.env_learning_amount, self.env_learn_on_add,
            self.stmem.length, self._novelty_threshold, self._own_threshold,
            self.jump, self.search_width, self.move_radius)
        ax.set_title("{} min distances: env_learn={} {}"
                     .format(self.name, self.env_learning_method,
                             self.env_learning_amount))
        ax.set_ylabel('min distance to preceding artifact')
        ax.set_xlabel('iteration')
        if self.logger is not None:
            imname = os.path.join(self.logger.folder, '{}_dists.png'.format(agent_vars))
            plt.savefig(imname)
            plt.close()
        else:
            plt.show()

    def plot_places(self):
        """Plot places where the agent has been and generated a spirograph.
        """
        fig, ax = plt.subplots()
        x = []
        y = []

        if len(self.arg_history) > 1:
            xs = []
            ys = []
            for p in self.arg_history:
                xs.append(p[0])
                ys.append(p[1])
            ax.plot(xs, ys, color=(0.0, 0.0, 1.0, 0.1))

        for a in self.A:
            if a.self_criticism == 'pass':
                args = a.framings[a.creator]['args']
                x.append(args[0])
                y.append(args[1])

        sc = ax.scatter(x, y, marker="x", color='red')
        ax.set_xlim([-200, 200])
        ax.set_ylim([-200, 200])

        agent_vars = "{}_{}_{}{}_last={}_stmem=list{}_veto={}_sc={}_jump={}_sw={}_mr={}_maxN".format(
            self.name, self.age, self.env_learning_method, self.env_learning_amount, self.env_learn_on_add,
            self.stmem.length, self._novelty_threshold, self._own_threshold,
            self.jump, self.search_width, self.move_radius)

        if self.logger is not None:
            imname = os.path.join(self.logger.folder, '{}.png'.format(agent_vars))
            plt.savefig(imname)
            plt.close()

            fname = os.path.join(self.logger.folder, '{}.txt'.format(agent_vars))
            with open(fname, "w") as f:
                f.write(" ".join([str(e) for e in xs]))
                f.write("\n")
                f.write(" ".join([str(e) for e in ys]))
                f.write("\n")
                f.write(" ".join([str(e) for e in x]))
                f.write("\n")
                f.write(" ".join([str(e) for e in y]))
                f.write("\n")
        else:
            plt.show()

    @aiomas.expose
    def close(self, folder):
        mean_dist, dists, indeces = self._artifact_distances()
        if len(dists) == 0:
            mean_dist = 0.0
        self._log(logging.INFO, "Mean of distances: {}".format(mean_dist))
        #self.plot_distances(mean_dist, dists, indeces)
        #self.plot_places()
        return mean_dist


class SpiroArtifact(Artifact):
    """Artifact class for Spirographs.
    """
    def __str__(self):
        return "Spirograph by: {} {}".format(self.creator,
                                            self.framings[self.creator])

    def __repr__(self):
        return self.__str__()

    def __lt__(self, other):
        return str(self) < str(other)

    def __eq__(self, other):
        return hash(str(self)) == hash(str(other))

    def __hash__(self):
        return hash(str(self))


class SpiroEnvManager(VoteManager):
    @aiomas.expose
    async def domain_artifact_added(self, artifact):
        rets = []
        for addr in self.get_agents():
            r_agent = await self.container.connect(addr, timeout=5)
            ret = await r_agent.domain_artifact_added(artifact)
            rets.append(ret)
        return rets



class SpiroMultiEnvManager(MultiEnvManager):