def _cut_and_choose(self, agent_a: Agent, agent_b: Agent, slices: List[CakeSlice]) -> CakeAllocation: """ Implements the Cut-and-Choose protocol on the given agents and the cake residue, returning an allocation of slices. This is an adapted implementation of `fairpy`'s `cut_and_choose.asymmetric_protocol`, modified to work with a cake residue and to match the helper classes used here. This was chosen over using `fairpy`'s implementation due to it working with a full cake rather than a residue, which impacts allocation as agents produce different satisfaction values based on the cake area. This is farther amplified by the small size of the residue which is left by the time this method is used in the algorithm (near the end). Thus making `cut_and_choose.asymmetric_protocol` incompatible with our requirements. :param agent_a: agent 1 to allocate to :param agent_b: agent 2 to allocate to :param slices: residue slices of the cake :return: allocation of the residue. """ allocation = CakeAllocation(slices) for slice in slices: mark_a = allocation.marking.mark( agent_a, slice, slice.value_according_to(agent_a) / 2) mark_b = allocation.marking.mark( agent_b, slice, slice.value_according_to(agent_b) / 2) if abs(mark_a.mark_position - mark_b.mark_position) < 0.0001: sliced = slice.slice_equally(agent_a, 2) allocation.set_slice_split(slice, sliced) allocation.allocate_slice(agent_a, sliced[0]) allocation.allocate_slice(agent_b, sliced[1]) else: cut_position = (mark_a.mark_position + mark_b.mark_position) / 2 sliced = slice.slice_at(cut_position) allocation.set_slice_split(slice, sliced) if mark_a.mark_position < mark_b.mark_position: allocation.allocate_slice(agent_a, sliced[0]) allocation.allocate_slice(agent_b, sliced[1]) else: allocation.allocate_slice(agent_a, sliced[1]) allocation.allocate_slice(agent_b, sliced[0]) return allocation
def allocate_all_partials_by_marks( allocation: CakeAllocation, marking: Marking ) -> Tuple[Dict[Agent, CakeSlice], List[List[CakeSlice]]]: """ Cuts all marked slices until the second-rightmost mark, and for each, the left slice (until that mark) is given to the agent who made the rightmost mark on the full slice. As defined in envy-free algorithm's main protocol line 18. :param allocation: allocation scope :param marking: marking context :return: a tuple composed of a map of agents to allocated slices, and the sliced parts >>> s = CakeSlice(0, 1) >>> s2 = CakeSlice(1, 1.5) >>> s3 = CakeSlice(1.7, 2) >>> a = PiecewiseConstantAgent([10, 10, 10], "agent") >>> a2 = PiecewiseConstantAgent([10, 10, 10], "agent2") >>> marking = Marking() >>> m1 = marking.mark(a, s, 5) >>> m2 = marking.mark(a, s2, 2.5) >>> m3 = marking.mark(a2, s, 2.5) >>> m4 = marking.mark(a2, s2, 5) >>> alloc = CakeAllocation([s, s2, s3]) >>> allocated, sliced = allocate_all_partials_by_marks(alloc, marking) >>> sliced [[(0,0.25), (0.25,1)], [(1,1.25), (1.25,1.5)]] >>> {agent.name(): slice for agent, slice in allocated.items()} {'agent': (0,0.25), 'agent2': (1,1.25)} """ allocated_slices = {} sliced_parts = [] for agent, slices in marking.rightmost_marks_by_agents().items(): for slice in slices: _, second_rightmost_pos = marking.second_rightmost_mark(slice) sliced = slice.slice_at(second_rightmost_pos) allocation.set_slice_split(slice, sliced) sliced_parts.append(sliced) allocation.allocate_slice(agent, sliced[0]) allocated_slices[agent] = sliced[0] return allocated_slices, sliced_parts
def _selfridge_conway(self, agents, residue: List[CakeSlice]) -> CakeAllocation: """ Implements the Selfridge-Conway on the given agents and the cake residue, returning an allocation of slices. :param agents: agents to allocate among :param residue: residue to allocate :return: allocation of the residue. """ p1 = agents[0] p2 = agents[1] p3 = agents[2] allocation = CakeAllocation(slice_equally(p1, 3, residue)) p2_slices_order = sorted(allocation.all_slices, key=lambda s: s.value_according_to(p2), reverse=True) # if p2 thinks the two largest slices are equal in value, allocation preferred by order p3,p2,p1 if abs(p2_slices_order[0].value_according_to(p2) - p2_slices_order[1].value_according_to(p2)) < 0.001: for agent in [p3, p2, p1]: favorite = find_favorite_slice(agent, allocation.unallocated_slices) allocation.allocate_slice(agent, favorite) return allocation # cut largest slice into 2, so that one part will be equal to the second largest slice_a = p2_slices_order[0] slice_b = p2_slices_order[1] slice_c = p2_slices_order[2] trimmings = slice_a.slice_to_value( p2, p2_slices_order[1].value_according_to(p2)) slice_a1 = trimmings[0] slice_a2 = trimmings[1] allocation.set_slice_split(slice_a, trimmings) # p3 chooses among the large slices large_slices = [slice_a1, slice_b, slice_c] favorite = find_favorite_slice(p3, large_slices) allocation.allocate_slice(p3, favorite) large_slices.remove(favorite) # p2 chooses among the large slices # if a1 was not chosen by p3, p2 must choose a1 if slice_a1 != favorite: agent_pa = p2 agent_pb = p3 allocation.allocate_slice(p2, slice_a1) large_slices.remove(slice_a1) else: agent_pa = p3 agent_pb = p2 favorite = find_favorite_slice(p2, large_slices) allocation.allocate_slice(p2, favorite) large_slices.remove(favorite) # p1 gets the last large slice allocation.allocate_slice(p1, large_slices.pop()) # pb slices a2 into 3 parts a2_sliced_parts = slice_a2.slice_equally(agent_pb, 3) # agent choose slices in order pa, p1, pb for agent in [agent_pa, p1, agent_pb]: favorite = find_favorite_slice(agent, a2_sliced_parts) allocation.allocate_slice(agent, favorite) return allocation
def allocate_by_rightmost_to_agent( agent: Agent, marked_slices: List[CakeSlice], allocation: CakeAllocation, marking: Marking ) -> Tuple[Dict[Agent, CakeSlice], List[List[CakeSlice]]]: """ Allocates slices by the rightmost rule, as described by envy-free algorithm's main protocol line 15: `agent` receives their preferred slice out of two slices where they made the rightmost mark, while the agent who made the second-rightmost mark on the preferred slice of `agent`, receives the second marked slice until its second-rightmost mark. Let's define `agent` who made the rightmost mark, on two slices (s1, s2). Each slice (of s1,s2) will have 2 marks at the least. The slices are split into to parts each at the location of the second-rightmost mark (s11 {second-rightmost mark} s12), (s21 {second-rightmost mark} s22). Define a as `agent`'s preferred slice out of s11 and s21, and b the other slice. `agent` is receives that slice. The agent who made the second-rightmost mark on slice a, receives slice b. :param agent: agent with 2 rightmost marks, one on each slice :param marked_slices: slices marked by agent :param allocation: the current allocation scope :param marking: the marking context :return: a tuple composed of a dict mapping agent to slice received, and a list of slices made >>> s = CakeSlice(0, 1) >>> s2 = CakeSlice(1, 1.5) >>> s3 = CakeSlice(1.7, 2) >>> a = PiecewiseConstantAgent([10, 10, 10], "agent") >>> a2 = PiecewiseConstantAgent([10, 10, 10], "agent2") >>> marking = Marking() >>> m = marking.mark(a, s, 10) >>> m = marking.mark(a, s2, 5) >>> m = marking.mark(a2, s, 5) >>> m = marking.mark(a2, s2, 2.5) >>> alloc = CakeAllocation([s, s2, s3]) >>> allocated, sliced = allocate_by_rightmost_to_agent(a, [s, s2, s3], alloc, marking) >>> sliced [[(0,0.5), (0.5,1)], [(1,1.25), (1.25,1.5)]] >>> {agent.name(): slice for agent, slice in allocated.items()} {'agent': (0,0.5), 'agent2': (1,1.25)} """ second_rightmost1_agent, second_rightmost1_pos = marking.second_rightmost_mark( marked_slices[0]) second_rightmost2_agent, second_rightmost2_pos = marking.second_rightmost_mark( marked_slices[1]) sliced1 = marked_slices[0].slice_at(second_rightmost1_pos) sliced2 = marked_slices[1].slice_at(second_rightmost2_pos) allocation.set_slice_split(marked_slices[0], list(sliced1)) allocation.set_slice_split(marked_slices[1], list(sliced2)) slice_option1 = sliced1[0] slice_option2 = sliced2[0] favorite = find_favorite_slice(agent, [slice_option1, slice_option2]) marking_agent_on_fav = second_rightmost1_agent if favorite == slice_option1 else second_rightmost2_agent other_slice = slice_option1 if favorite == slice_option2 else slice_option2 allocation.allocate_slice(agent, favorite) allocation.allocate_slice(marking_agent_on_fav, other_slice) return { agent: favorite, marking_agent_on_fav: other_slice }, [sliced1, sliced2]