Example #1
0
    def __init__(self, agent: Agent):
        """

        :param agent: An agent to normalize it's values
        """
        assert agent is not None
        super().__init__(agent.name())
        self.__agent = agent
Example #2
0
    def _correction(self, cutter: Agent, allocation: CakeAllocation,
                    total_allocation: CakeAllocation):
        """
        Runs the correction protocol of "An Improved Envy-Free Cake Cutting Protocol for Four Agents".

        Define A and B as the agents who made 2 marks on the allocated insignificant slice, where
        A is the one who received that allocation.
        Transfer the slice to B.

        If there are no more partial slices (other than the insignificant slice), agents
        C, A and D are allocated their favorite slices out of all the slices in the allocation,
        in order.

        Otherwise if there is another partial slice, let E be the agent who made the rightmost mark on
        it other than B.
        Allocate that slice to E.

        The remaining non-cutter chooses their favorite out of the 2 remaining slices and receives it.
        The cutter receives the last slice.

        :param cutter: cutter of the allocation to fix
        :param allocation: allocation to fix
        :param total_allocation: the complete state of the cake
        :return: void

        >>> a = PiecewiseConstantAgent([10, 10, 10], "test1")
        >>> a2 = PiecewiseConstantAgent([10, 10, 10], "test2")
        >>> a3 = PiecewiseConstantAgent([10, 10, 10], "test3")
        >>> a4 = PiecewiseConstantAgent([10, 10, 10], "test4")
        >>> s = CakeSlice(0,0.75)
        >>> s2 = CakeSlice(0.75,1.5)
        >>> s3 = CakeSlice(1.5,2)
        >>> s4 = CakeSlice(2,2.25)
        >>> s5 = CakeSlice(2.25,3)
        >>> algo = Algorithm([a, a2, a3, a4, s5], logging.getLogger("test"))
        >>> alloc = CakeAllocation([s, s2, s3, s4, s5])
        >>> alloc.allocate_slice(a, s)
        >>> alloc.allocate_slice(a2, s2)
        >>> alloc.allocate_slice(a3, s3)
        >>> alloc.allocate_slice(a4, s4)
        >>> sorted([agent.name() for agent in alloc.agents_with_allocations])
        ['test1', 'test2', 'test3', 'test4']
        >>> m = alloc.marking.mark(a3, s4, 2)
        >>> m = alloc.marking.mark(a3, s4, 1)
        >>> algo._correction(s, alloc, alloc)
        >>> alloc.unallocated_slices
        [(2.25,3)]
        >>> sorted([agent.name() for agent in alloc.agents_with_allocations])
        ['test2', 'test3']
        >>> [alloc.get_allocation_for_agent(agent) for agent in [a, a2, a3, a4]]
        [[], [(0,0.75), (0.75,1.5)], [(1.5,2), (2,2.25)], []]
        """
        agent_with_insignificant = allocation.try_get_agent_with_insignificant_slice(
        )
        insignificant_slice = allocation.get_insignificant_slice(
            agent_with_insignificant)
        marks = allocation.marking.marks_on_slice(insignificant_slice)

        self._logger.info("{} has insignificant {}, with marks {}".format(
            agent_with_insignificant.name(), str(insignificant_slice),
            str([mark[1] for mark in marks])))
        if len(marks) != 2:
            raise ValueError("Slice should have 2 marks")
        # find insignificant slice
        # A, B agents who marked that slice
        # B is allocated the slice

        agent_a = agent_with_insignificant
        agent_b = [mark[0] for mark in marks if mark[0] != agent_a][0]

        self._logger.info("{} is allocated insignificant".format(
            agent_b.name()))

        total_allocation.allocate_slice(agent_b, insignificant_slice)

        remaining_agents = exclude_from_list(self._agents, [agent_a, agent_b])
        agent_c = remaining_agents[0]
        agent_d = remaining_agents[1]
        # if no other slice is marked
        #   agents choose fav by C, A, D
        # only the insignificant slice remains
        if len(allocation.partial_slices) <= 1:
            self._logger.info("no other partial slices")
            for agent in [agent_c, agent_a, agent_d]:
                if len(allocation.all_slices) > 0:
                    favorite = find_favorite_slice(agent,
                                                   allocation.all_slices)
                    total_allocation.allocate_slice(agent, favorite)
                    self._logger.info("{} choose and received {}".format(
                        agent.name(), str(favorite)))

            self._logger.info("correction finished with {}".format(', '.join(
                "{}: {}".format(
                    agent.name(),
                    str(allocation.get_allocation_for_agent(agent)))
                for agent in allocation.agents_with_allocations)))
        # else
        #   find rightmost mark not made by B on the other partial piece
        #   E = agent who made it
        #   E is allocated partial piece
        #   last non-cutter chooses their favorite among 2 complete slices
        #   the cutter is allocated the remaining piece
        else:
            other_slice = exclude_from_list(allocation.partial_slices,
                                            [insignificant_slice])[0]
            self._logger.info("{} is the other partial slice")

            agent_e, mark_made = [
                (agent, mark)
                for agent, mark in allocation.marking.marks_on_slice(
                    other_slice) if agent != agent_b
            ][0]
            self._logger.info(
                "{} made mark {} on slice and receives it".format(
                    agent_e.name(), str(mark_made)))
            total_allocation.allocate_slice(agent_e, other_slice)

            last_non_cutter = exclude_from_list(self._agents,
                                                [cutter, agent_e, agent_b])[0]
            favorite = find_favorite_slice(last_non_cutter[0],
                                           allocation.free_complete_slices)
            self._logger.info(
                "{} last non cutter choose and received {}".format(
                    last_non_cutter.name, str(favorite)))
            total_allocation.allocate_slice(last_non_cutter, favorite)

            self._logger.info("{} (cutter) receives {}".format(
                cutter.name(), str(allocation.free_complete_slices[0])))
            total_allocation.allocate_slice(cutter,
                                            allocation.free_complete_slices[0])

            self._logger.info("correction finished with {}".format(', '.join(
                "{}: {}".format(
                    agent.name(),
                    str(allocation.get_allocation_for_agent(agent)))
                for agent in allocation.agents_with_allocations)))
Example #3
0
    def _core(self,
              cutter: Agent,
              residue: List[CakeSlice],
              agents: List[Agent],
              exclude_from_competition: Agent = None) -> CakeAllocation:
        """
        Runs the core protocol of "An Improved Envy-Free Cake Cutting Protocol for Four Agents".

        Starts by having `cutter` cut the residue into 4 slices which they view as equal in value.
        The preferences of the other agents are then explored. Agents whose first preference is not
        conflicted with other agents receive their favorite slice.

        If all the agents received their favorite slice, return. Otherwise, we start the competition phase.

        For each of the non-cutter agents, we compare preferences.
        Agent who:
            - has not competition on the second preference, or
            - has 1 competition on the second preference, the competing agent also considers the slice as their second
            preference, and both have 1 competition for their first preference

        will make a "2-mark" on the first preference, which is a mark that makes the left part of the slice
        equal in value to the second preference.
        Other agents make a "3-mark" on the second preference, which is a mark that markes the left part of that
        preference equal in value to the third preference.

        Now allocate the slices by the rightmost rule:

        Find an agent which has made the rightmost mark on 2 slices. If one was found,
        out of those 2 slices, cut them until the second rightmost mark on each slice. Said
        agent will receive the preferred out of the two slices. The other slice is given to the
        agent who made the second rightmost mark on the slice taken by the previous agent.

        If there is no agent with rightmost marks on 2 slices, all the slices with marks are
        cut until the second rightmost mark, and each is allocated to the agent who made the rightmost mark.

        If any non-cutters remain that did not receive any slice yet, they are each given their preferred
        slice, out of the remaining uncut slices, in arbitrary order.

        Now the cutter is given the last unallocated complete slice, and the allocation
        of slices is returned.

        :param cutter: agent responsible for cutting
        :param residue: remaining slices of the cake
        :param agents: agents participating in the current allocation
        :param exclude_from_competition: agent to exclude from the competition stage.
        :return: an allocation of cake slices.

        >>> a = PiecewiseConstantAgent([10, 10, 10], "test1")
        >>> a2 = PiecewiseConstantAgent([10, 10, 10], "test2")
        >>> a3 = PiecewiseConstantAgent([10, 10, 10], "test3")
        >>> a4 = PiecewiseConstantAgent([10, 10, 10], "test4")
        >>> s = CakeSlice(0, 3)
        >>> algo = Algorithm([a, a2, a3, a4], logging.getLogger("test"))
        >>> alloc = algo._core(a, [s], [a2, a3, a4])
        >>> alloc.unallocated_slices
        []
        >>> sorted([agent.name() for agent in alloc.agents_with_allocations])
        ['test1', 'test2', 'test3', 'test4']
        >>> [alloc.get_allocation_for_agent(agent) for agent in [a, a2, a3, a4]]
        [[(2.25,3)], [(0.75,1.5)], [(0,0.75)], [(1.5,2.25)]]
        """
        active_agents = exclude_from_list(agents, [cutter])

        self._logger.info(
            "Starting core with cutter {} and agents {} on residue {}".format(
                cutter.name(),
                ', '.join([agent.name() for agent in active_agents]),
                str(residue)))

        # residue may not be a complete slice
        # cutter slices into 4
        slices = slice_equally(cutter, 4, residue)
        self._logger.info("Residue sliced into {}".format(str(slices)))

        preferences = get_preferences_for_agents(active_agents, slices)
        allocation = CakeAllocation(slices)

        satisfied_agents = []
        for agent in active_agents:
            preference = preferences.get_preference_for_agent(agent)

            favorite = preference[0]
            favorite_conflicts_first, _ = preferences \
                .find_agents_with_preference_for(favorite,
                                                 exclude_agents=[agent, exclude_from_competition])

            # line 8 if, not talking about lack of conflict for primary slice...
            if len(favorite_conflicts_first) == 0:
                allocation.allocate_slice(agent, favorite)
                satisfied_agents.append(agent)
                self._logger.info(
                    "{} has preference {} with no conflicts, allocating".
                    format(agent.name(), favorite))
            else:
                self._logger.info(
                    "{} has preference {} with conflicts, not allocating".
                    format(agent.name(), favorite))

        for agent in satisfied_agents:
            active_agents.remove(agent)

        if len(active_agents) == 0:
            self._logger.info(
                "all agents satisfied, allocating {} to cutter {}".format(
                    str(allocation.free_complete_slices[0]), cutter.name()))
            allocation.allocate_slice(cutter,
                                      allocation.free_complete_slices[0])

            self._logger.info("core finished with {}".format(', '.join(
                "{}: {}".format(
                    agent.name(),
                    str(allocation.get_allocation_for_agent(agent)))
                for agent in allocation.agents_with_allocations)))
            return allocation

        # Conflict

        # do marking
        exclude = list(satisfied_agents)
        exclude.append(exclude_from_competition)
        self._logger.info("Starting conflict handling, excluding {}".format(
            ', '.join([agent.name() for agent in filter(None, exclude)])))

        for agent in exclude_from_list(active_agents,
                                       [exclude_from_competition]):
            slice, mark = mark_by_preferences(agent, preferences,
                                              allocation.marking, exclude)
            self._logger.info("{} made mark at {} on slice {}".format(
                agent.name(), str(mark), str(slice)))

        # Allocate by rightmost rule
        self._logger.info("Starting allocation by rightmost rule")

        agents_to_rightmost_marked_slices = allocation.marking.rightmost_marks_by_agents(
        )

        allocated = False
        for agent, slices in agents_to_rightmost_marked_slices.items():
            if len(slices) != 2:
                continue
            self._logger.info("{} has rightmost mark on 2 slices {}".format(
                agent.name(), str(slices)))

            agents_to_allocated, sliced = allocate_by_rightmost_to_agent(
                agent, slices, allocation, allocation.marking)

            self._logger.info("slices cut into {}".format(str(sliced)))
            self._logger.info("allocated {}".format(', '.join([
                '{}: {}'.format(agent.name(), str(slice))
                for agent, slice in agents_to_allocated.items()
            ])))
            allocated = True
            break

        if not allocated:
            self._logger.info("No agent with rightmost mark on 2 slices")
            agents_to_allocated, sliced = allocate_all_partials_by_marks(
                allocation, allocation.marking)
            self._logger.info("slices cut into {}".format(str(sliced)))
            self._logger.info("allocated {}".format(', '.join([
                '{}: {}'.format(agent.name(), str(slice))
                for agent, slice in agents_to_allocated.items()
            ])))

        # if any non-cutters not given any slices
        #   each should choose favorite un-allocated complete slice
        for agent in exclude_from_list(active_agents,
                                       [exclude_from_competition]):
            if agent not in allocation.agents_with_allocations:
                favorite = find_favorite_slice(agent,
                                               allocation.free_complete_slices)
                allocation.allocate_slice(agent, favorite)

                self._logger.info(
                    "{} has not received slice yet, chose and received {}".
                    format(agent.name(), str(favorite)))

        # cutter given remaining un-allocated complete slice
        self._logger.info("cutter receives {}".format(
            str(allocation.free_complete_slices[0])))
        allocation.allocate_slice(cutter, allocation.free_complete_slices[0])

        self._logger.info("core finished with {}".format(', '.join(
            "{}: {}".format(agent.name(),
                            str(allocation.get_allocation_for_agent(agent)))
            for agent in allocation.agents_with_allocations)))
        return allocation