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 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 def vote_IRV(candidates, votes, n_winners):
    """Perform IRV voting based on votes.

    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
    """
    # TODO: Check what is wrong in here.
    votes = [[e[0] for e in v] for v in votes]
    f = lambda x: Counter(e[0] for e in x).most_common()
    cl = list(candidates)
    ranking = []
    fp = f(votes)
    fpl = [e[0] for e in fp]

    while len(fpl) > 1:
        _remove_zeros(votes, fpl, cl, ranking)
        _remove_last(votes, fpl, cl, ranking)
        cl = fpl[:-1]
        fp = f(votes)
        fpl = [e[0] for e in fp]

    ranking.append((fpl[0], len(ranking) + 1))
    ranking = list(reversed(ranking))
    return ranking[:min(n_winners, len(ranking))] def vote_best(candidates, votes, n_winners):
    """Select the artifact with the single best evaluation as the winner of
    the vote.

    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
    """
    best = [votes[0][0]]
    for v in votes[1:]:
        if v[0][1] > best[0][1]:
            best = [v[0]]
    return best def vote_least_worst(candidates, votes, n_winners):
    """Select "least worst" artifact as the winner of the vote.

    Least worst artifact is the artifact with the best worst evaluation, i.e.
    its worst evaluation is the best among all of the artifacts.

    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
    """
    worsts = {str(c): 100000000.0 for c in candidates}
    for v in votes:
        for e in v:
            if worsts[str(e[0])] > e[1]:
                worsts[str(e[0])] = e[1]
    s = sorted(worsts.items(), key=lambda x: x[1], reverse=True)
    best = s[:min(n_winners, len(candidates))]
    d = []
    for e in best:
        for c in candidates:
            if str(c) == e[0]:
                d.append((c, e[1]))
    return d def vote_random(candidates, votes, n_winners):
    """Select random winners from the candidates.

    This voting method bypasses the given votes completely.

    :param candidates: All candidates in the vote
    :param votes: Votes from the agents, which are omitted by randomized voting
    :param int n_winners: The number of vote winners
    """
    random_candidates = list(candidates)
    shuffle(random_candidates)
    random_candidates = random_candidates[:min(n_winners, len(random_candidates))]
    best = [(i, 0.0) for i in random_candidates]
    return best 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) def artifact_serializer():
    """Basic serializer for :class¨:`~creamas.core.artifact.Artifact` objects
    using pickle.

    This serializer requires attr:`~aiomas.codecs.MsgPack` codec to work.
    """
    return Artifact, pickle.dumps, pickle.loads
import asyncio
import unittest
import aiomas
from random import choice, random
from creamas.vote import VoteAgent, VoteEnvironment, VoteOrganizer
from creamas.vote import vote_mean, vote_IRV, vote_best, vote_least_worst, vote_random
from creamas.core.artifact import Artifact
from creamas.core.environment import Environment
from creamas.serializers import artifact_serializer
        self.assertEqual(len(self.env.candidates), 0)

        # Gather votes works: one vote from each agent in environment, each
        # vote having evaluation for each of the candidates
        a0.add_candidate(c0)
        a1.add_candidate(c1)
        a2.add_candidate(c2)
        a1.add_candidate(c3)
        votes = self.env.gather_votes(self.env.candidates)
        self.assertEqual(len(votes), len(self.env.get_agents()))
        for v in votes:
            self.assertEqual(len(v), len(self.env.candidates))
            for cand in self.env.candidates:
                self.assertIn(cand, [e[0] for e in v])

        a2.add_candidate(c4)
        # Validate candidates works. Should return only three candidates as
        # c3 gets rejected by a0, and c4 by a0 and a1
        valid = self.env.validate_candidates(self.env.candidates)
        self.assertEqual(len(valid), 3)
        self.assertNotIn(c3, valid)
        self.assertNotIn(c4, valid)

    def test_vote_vo(self):
        '''Test VoteOrganizer
        '''
        a0 = VoteTestAgent(self.env, n=0)
        a1 = VoteTestAgent(self.env, n=1)
        a2 = VoteTestAgent(self.env, n=2)
        c0 = Artifact(a0, 0)
        c1 = Artifact(a1, 1)
        c2 = Artifact(a2, 2)
        c3 = Artifact(a1, 3)
        c4 = Artifact(a2, 4)

        # Adding and clearing candidates works
        a1.add_candidate(c1)
        self.vo.gather_candidates()
        self.assertEqual(len(self.vo.candidates), 1)
        a2.add_candidate(c2)
        self.vo.gather_candidates()
        self.assertEqual(len(self.vo.candidates), 2)
        self.vo.clear_candidates(clear_env=False)
        self.assertEqual(len(self.vo.candidates), 0)
        self.assertEqual(len(self.env.candidates), 2)
        self.vo.clear_candidates(clear_env=True)
        self.assertEqual(len(self.vo.candidates), 0)
        self.assertEqual(len(self.env.candidates), 0)

        # Gather votes works: one vote from each agent in environment, each
        # vote having evaluation for each of the candidates
        a0.add_candidate(c0)
        a1.add_candidate(c1)
        a2.add_candidate(c2)
        a1.add_candidate(c3)
        self.vo.gather_candidates()
        self.vo.gather_votes()
        self.assertEqual(len(self.vo.votes), len(self.env.get_agents()))
        for v in self.vo.votes:
            self.assertEqual(len(v), len(self.env.candidates))
            for cand in self.env.candidates:
                self.assertIn(cand, [e[0] for e in v])

        a2.add_candidate(c4)
        # Validate candidates works. Should leave only three candidates as
        # c3 gets rejected by a0, and c4 by a0 and a1
        self.vo.validate_candidates()
        self.assertEqual(len(self.vo.candidates), 3)
        self.assertNotIn(c3, self.vo.candidates)
        self.assertNotIn(c4, self.vo.candidates)

        # Gathering votes for zero candidates sets votes to empty list
        self.vo.clear_candidates(clear_env=True)
        self.vo.gather_votes()
        self.assertEqual(len(self.vo.votes), 0)

    def test_vote_methods(self):
        '''Test different predefined voting methods.
        '''
        a0 = VoteTestAgent(self.env, n=0)
        a1 = VoteTestAgent(self.env, n=1)
        a2 = VoteTestAgent(self.env, n=2)
        c0 = Artifact(a0, 0)
        c1 = Artifact(a1, 1)
        c2 = Artifact(a2, 2)
        c3 = Artifact(a1, 3)
        c4 = Artifact(a2, 4)

        a0.add_candidate(c2)
        a1.add_candidate(c3)
        a2.add_candidate(c4)
        a0.add_candidate(c1)
        a0.add_candidate(c0)
        self.vo.gather_candidates()
        self.vo.gather_votes()
        winners = self.vo.compute_results(vote_random, winners=3)
        self.assertEqual(len(winners), 3)
        winners = self.vo.compute_results(vote_random, winners=5)
        self.assertEqual(len(winners), 5)
        winners = self.vo.compute_results(vote_random, winners=1)
        self.assertEqual(len(winners), 1)
        self.vo.clear_candidates(clear_env=True)

        a0.add_candidate(c2)
        a1.add_candidate(c3)
        a2.add_candidate(c4)
        self.vo.gather_candidates()
        self.vo.gather_votes()
        winners = self.vo.compute_results(vote_best, winners=1)
        winner = winners[0][0]
        res = winners[0][1]
        self.assertEqual(c2, winner)
        self.assertEqual(3, res)
        self.vo.clear_candidates(clear_env=True)

        a0.add_candidate(c0)
        a1.add_candidate(c1)
        a2.add_candidate(c2)
        self.vo.gather_candidates()
        self.vo.gather_votes()

        winners = self.vo.compute_results(vote_least_worst, winners=1)