Exemple #1
0
def max_welfare_allocation_for_families(instance, families:list, welfare_function, welfare_constraint_function=None) -> AllocationToFamilies:
    """
    Find an allocation maximizing a given social welfare function. (aka Max Nash Welfare) allocation.
    :param agents: a matrix v in which each row represents an agent, each column represents an object, and v[i][j] is the value of agent i to object j.
    :param families: a list of lists. Each list represents a family and contains the indices of the agents in the family.
    :param welfare_function:   a monotonically-increasing function w: R -> R representing the welfare function to maximize.
    :param welfare_constraint: a predicate w: R -> {true,false} representing an additional constraint on the utility of each agent.

    :return allocation_matrix:  a matrix alloc of a similar shape in which alloc[i][j] is the fraction allocated to agent i from object j.

    For usage examples, see the function max_minimum_allocation_for_families.
    """
    v = ValuationMatrix(instance)
    num_of_families = len(families)
    agent_to_family = map_agent_to_family(families, v.num_of_agents)

    alloc = cvxpy.Variable((num_of_families, v.num_of_objects))
    feasibility_constraints = [
        sum([alloc[f][o] for f in range(num_of_families)])==1
        for o in v.objects()
    ]
    positivity_constraints = [
        alloc[f][o] >= 0 for f in range(num_of_families)
        for o in v.objects()
    ]
    utilities = [sum([alloc[agent_to_family[i]][o]*v[i][o] for o in v.objects()]) for i in v.agents()]

    if welfare_constraint_function is not None:
        welfare_constraints = [welfare_constraint_function(utility) for utility in utilities]
    else:
        welfare_constraints = []
    max_welfare = maximize(welfare_function(utilities), feasibility_constraints+positivity_constraints+welfare_constraints)
    logger.info("Maximum welfare is %g",max_welfare)
    return AllocationToFamilies(v, alloc.value, families)
Exemple #2
0
def divide(v: ValuationMatrix) -> List[List[int]]:
    """"
    In stage 1 the divider agent having index 0 partitions the goods into bundles.
    >>> divide(ValuationMatrix([[0.5, 0, 0.5], [1/3, 1/3, 1/3]]))
    [[1, 0], [2]]
    >>> divide(ValuationMatrix([[0.25, 0.25, 0.25, 0.25, 0, 0], [0.25, 0, 0.26, 0, 0.25, 0.24], [0.25, 0, 0.24, 0, 0.25, 0.26]]))
    [[4, 5, 0], [1], [2, 3]]
    """
    total_value = v.verify_normalized()
    item_order = sorted(v.objects(), key=lambda j: v[0, j])

    bundles = []
    divided_items_count = 0
    divided_value = 0
    for bundle_index in v.agents():
        bundle_value = 0
        item_index = divided_items_count
        while item_index < v.num_of_objects and (
                bundle_value + v[0, item_order[item_index]]) * (
                v.num_of_agents - bundle_index) + divided_value <= total_value:
            bundle_value += v[0, item_order[item_index]]
            item_index += 1

        bundles.append(
            list(map(lambda t: item_order[t], range(divided_items_count, item_index))))
        divided_items_count = item_index
        divided_value += bundle_value
    return bundles
Exemple #3
0
def one_directional_bag_filling(values, thresholds:List[float]):
	"""
	The simplest bag-filling procedure: fills a bag in the given order of objects.
	
	:param valuations: a valuation matrix (a row for each agent, a column for each object).
	:param thresholds: determines, for each agent, the minimum value that should be in a bag before the agent accepts it.

	>>> one_directional_bag_filling(values=[[11,33],[44,22]], thresholds=[30,30])
	Agent #0 gets {1} with value 33.
	Agent #1 gets {0} with value 44.
	<BLANKLINE>
	>>> one_directional_bag_filling(values=[[11,33],[44,22]], thresholds=[10,10])
	Agent #0 gets {0} with value 11.
	Agent #1 gets {1} with value 22.
	<BLANKLINE>
	>>> one_directional_bag_filling(values=[[11,33],[44,22]], thresholds=[40,30])
	Agent #0 gets {} with value 0.
	Agent #1 gets {0} with value 44.
	<BLANKLINE>
	"""
	values = ValuationMatrix(values)
	if len(thresholds) != values.num_of_agents:
		raise ValueError(f"Number of valuations {values.num_of_agents} differs from number of thresholds {len(thresholds)}")

	allocation = SequentialAllocation(values.agents(), values.objects(), logger)
	bag = Bag(values, thresholds)
	while True:
		(willing_agent, allocated_objects) = bag.fill(allocation.remaining_objects, allocation.remaining_agents)
		if willing_agent is None:  break
		allocation.let_agent_get_objects(willing_agent, allocated_objects)
		bag.reset()
	return Allocation(values, allocation.bundles)
Exemple #4
0
	def __init__(self, values:ValuationMatrix, thresholds:List[float]):
		"""
		Initialize an empty bag.
		:param values: a matrix representing additive valuations (a row for each agent, a column for each object).
		:param thresholds: determines, for each agent, the minimum value that should be in a bag before the agent accepts it.
		"""
		self.values = ValuationMatrix(values)
		self.thresholds = thresholds
		self.reset()
Exemple #5
0
def max_welfare_allocation(instance:Any, welfare_function, welfare_constraint_function=None) -> Allocation:
    """
    Find an allocation maximizing a given social welfare function. (aka Max Nash Welfare) allocation.
    :param agents: a matrix v in which each row represents an agent, each column represents an object, and v[i][j] is the value of agent i to object j.
    :param welfare_function:   a monotonically-increasing function w: R -> R representing the welfare function to maximize.
    :param welfare_constraint: a predicate w: R -> {true,false} representing an additional constraint on the utility of each agent.

    :return allocation_matrix:  a matrix alloc of a similar shape in which alloc[i][j] is the fraction allocated to agent i from object j.

    For usage examples, see the functions max_sum_allocation, max_product_allocation, max_minimum_allocation.
    """
    v = ValuationMatrix(instance)
    allocation_vars = cvxpy.Variable((v.num_of_agents, v.num_of_objects))
    feasibility_constraints = [
        sum([allocation_vars[i][o] for i in v.agents()])==1
        for o in v.objects()
    ]
    positivity_constraints = [
        allocation_vars[i][o] >= 0 for i in v.agents()
        for o in v.objects()
    ]
    utilities = [sum([allocation_vars[i][o]*v[i][o] for o in v.objects()]) for i in v.agents()]
    if welfare_constraint_function is not None:
        welfare_constraints = [welfare_constraint_function(utility) for utility in utilities]
    else:
        welfare_constraints = []
    max_welfare = maximize(welfare_function(utilities), feasibility_constraints+positivity_constraints+welfare_constraints)
    logger.info("Maximum welfare is %g",max_welfare)
    allocation_matrix = allocation_vars.value
    return allocation_matrix
Exemple #6
0
 def __init__(self, valuation_matrix, tolerance=0.01):
     valuation_matrix = ValuationMatrix(valuation_matrix)
     mpa = max_product_allocation(valuation_matrix)
     mpa_utilities = mpa.utility_profile()
     logger.info(
         "The max-product allocation is:\n%s,\nwith utility profile: %s",
         mpa, mpa_utilities)
     thresholds = mpa_utilities * (1 - tolerance)
     logger.info("The thresholds are: %s", thresholds)
     logger.info("The proportionality thresholds are: %s", [
         sum(valuation_matrix[i]) / valuation_matrix.num_of_agents
         for i in valuation_matrix.agents()
     ])
     self.tolerance = tolerance
     super().__init__(valuation_matrix, thresholds)
 def __init__(self, valuation_matrix):
     valuation_matrix = ValuationMatrix(valuation_matrix)
     self.valuation = valuation_matrix
     self.min_sharing_number = valuation_matrix.num_of_agents
     self.min_sharing_allocation = None
     self.graph_generator = GraphGenerator(valuation_matrix)
     self.find = False
Exemple #8
0
def leximin_optimal_allocation(instance:Any) -> Allocation:
    """
    Find the leximin-optimal (aka Egalitarian) allocation.
    :param instance: a matrix v in which each row represents an agent, each column represents an object, and v[i][j] is the value of agent i to object j.

    :return allocation_matrix:  a matrix alloc of a similar shape in which alloc[i][j] is the fraction allocated to agent i from object j.
    The allocation should maximize the leximin vector of utilities.
    >>> logger.setLevel(logging.WARNING)
    >>> a = leximin_optimal_allocation([[5,0],[3,3]]).round(3)
    >>> a
    Agent #0 gets { 75.0% of 0} with value 3.75.
    Agent #1 gets { 25.0% of 0, 100.0% of 1} with value 3.75.
    <BLANKLINE>
    >>> a.matrix
    [[0.75 0.  ]
     [0.25 1.  ]]
    >>> a.utility_profile()
    array([3.75, 3.75])
    >>> v = [[3,0],[5,5]]
    >>> print(leximin_optimal_allocation(v).round(3).utility_profile())
    [3. 5.]
    >>> v = [[5,5],[3,0]]
    >>> print(leximin_optimal_allocation(v).round(3).utility_profile())
    [5. 3.]
    >>> v = [[3,0,0],[0,4,0],[5,5,5]]
    >>> print(leximin_optimal_allocation(v).round(3).utility_profile())
    [3. 4. 5.]
    >>> v = [[4,0,0],[0,3,0],[5,5,10],[5,5,10]]
    >>> print(leximin_optimal_allocation(v).round(3).utility_profile())
    [4. 3. 5. 5.]
    >>> v = [[3,0,0],[0,3,0],[5,5,10],[5,5,10]]
    >>> a = leximin_optimal_allocation(v)
    >>> print(a.round(3).utility_profile())
    [3. 3. 5. 5.]
    >>> v = [[1/3, 0, 1/3, 1/3],[1, 1, 1, 0]]
    >>> a = leximin_optimal_allocation(v)
    >>> logger.setLevel(logging.WARNING)
    """
    v = ValuationMatrix(instance)
    allocation_vars = cvxpy.Variable((v.num_of_agents, v.num_of_objects))
    feasibility_constraints = [
        sum([allocation_vars[i][o] for i in v.agents()])==1
        for o in v.objects()
    ]
    positivity_constraints = [
        allocation_vars[i][o] >= 0 for i in v.agents()
        for o in v.objects()
    ]
    utilities = [sum([allocation_vars[i][o]*v[i][o] for o in v.objects()]) for i in v.agents()]
    leximin_solve(objectives=utilities, constraints=feasibility_constraints+positivity_constraints)
    allocation_matrix = allocation_vars.value
    return allocation_matrix
Exemple #9
0
def proportional_allocation_with_bounded_sharing(instance: Any,
                                                 entitlements: List = None
                                                 ) -> Allocation:
    """
    Finds a Pareto-optimal and proportional allocation
          with at most n-1 sharings, where n is the number of agents.

    :param instance: a valuation profile in any supported format.
    :param entitlements: the entitlement of each agent. Optional, default is (1,...,1) which means equal entitlements.

    IDEA: find a Basic Feasible Solution (BFS) of a linear program.
    NOTE: some solvers return a BFS by default (particularly, those running Simplex).

    >>> logger.setLevel(logging.WARNING)
    >>> instance = [[8,2],[5,5]]
    >>> proportional_allocation_with_bounded_sharing(instance).round(3)
    Agent #0 gets { 62.5% of 0} with value 5.
    Agent #1 gets { 37.5% of 0, 100.0% of 1} with value 6.88.
    <BLANKLINE>
    >>> proportional_allocation_with_bounded_sharing(instance, entitlements=[4,1]).round(3)
    Agent #0 gets { 100.0% of 0} with value 8.
    Agent #1 gets { 100.0% of 1} with value 5.
    <BLANKLINE>
    >>> proportional_allocation_with_bounded_sharing(instance, entitlements=[3,2]).round(3)
    Agent #0 gets { 75.0% of 0} with value 6.
    Agent #1 gets { 25.0% of 0, 100.0% of 1} with value 6.25.
    <BLANKLINE>
    """
    v = ValuationMatrix(instance)
    if entitlements is None:
        entitlements = np.ones(v.num_of_agents)
    else:
        entitlements = np.array(entitlements)
    entitlements = entitlements / sum(entitlements)  # normalize
    logger.info("Normalized entitlements: %s", entitlements)
    logger.info("Total values: %s", v.total_values())
    thresholds = v.total_values() * entitlements
    logger.info("Value thresholds: %s", thresholds)
    return dominating_allocation_with_bounded_sharing(v, thresholds)
Exemple #10
0
def compute_all_ratios(valuation_matrix) -> list:
    """
    Creates a list of matrices.
    Each matrix is the ratio between agent i and all the other agents.
    For example:   
       ans[3] = matrix of the ratio between agent #3 and all ether agents.
       So ans[3][4] = the ratio array between agent 3 to agent 4.
    :param valuation_matrix: the valuation of the agents.
    :return: ans - list of all the matrices.
    >>> v = [[1,2],[3,4]]
    >>> compute_all_ratios(v)
    [[[(0, 1.0), (1, 1.0)], [(0, 0.3333333333333333), (1, 0.5)]], [[(0, 3.0), (1, 2.0)], [(0, 1.0), (1, 1.0)]]]
    >>> v = [[1,0],[3,7]]
    >>> compute_all_ratios(v)
    [[[(0, 1.0), (1, 1.0)], [(0, 0.3333333333333333), (1, 0.0)]], [[(0, 3.0), (1, inf)], [(0, 1.0), (1, 1.0)]]]
    >>> v = [[1,0,2],[3,7,2.5],[4,2,0]]
    >>> compute_all_ratios(v)
    [[[(0, 1.0), (1, 1.0), (2, 1.0)], [(0, 0.3333333333333333), (1, 0.0), (2, 0.8)], [(0, 0.25), (1, 0.0), (2, inf)]], [[(0, 3.0), (1, inf), (2, 1.25)], [(0, 1.0), (1, 1.0), (2, 1.0)], [(0, 0.75), (1, 3.5), (2, inf)]], [[(0, 4.0), (1, inf), (2, 0.0)], [(0, 1.3333333333333333), (1, 0.2857142857142857), (2, 0.0)], [(0, 1.0), (1, 1.0), (2, 1.0)]]]
    """
    valuation_matrix = ValuationMatrix(valuation_matrix)
    ans = []
    for i in valuation_matrix.agents():
        mat = np.zeros((valuation_matrix.num_of_agents,
                        valuation_matrix.num_of_objects)).tolist()
        for j in valuation_matrix.agents():
            for k in valuation_matrix.objects():
                if (valuation_matrix[i][k] == 0) and (valuation_matrix[j][k]
                                                      == 0):
                    temp = 1.0
                else:
                    if (valuation_matrix[j][k] == 0):
                        temp = np.inf
                    else:
                        temp = valuation_matrix[i][k] / valuation_matrix[j][k]
                mat[j][k] = (k, temp)
        ans.append(mat)
    return ans
Exemple #11
0
def leximin_optimal_allocation_for_families(instance:Any, families:list) -> AllocationToFamilies:
    """
    Find the leximin-optimal (aka Egalitarian) allocation among families.
    :param agents: a matrix v in which each row represents an agent, each column represents an object, and v[i][j] is the value of agent i to object j.
    :param families: a list of lists. Each list represents a family and contains the indices of the agents in the family.

    :return allocation_matrix:  a matrix alloc of a similar shape in which alloc[i][j] is the fraction allocated to agent i from object j.
    The allocation should maximize the leximin vector of utilities.
    >>> families = [ [0], [1] ]  # two singleton families
    >>> v = [[5,0],[3,3]]
    >>> print(leximin_optimal_allocation_for_families(v,families).round(3).utility_profile())
    [3.75 3.75]
    >>> v = [[3,0],[5,5]]
    >>> print(leximin_optimal_allocation_for_families(v,families).round(3).utility_profile())
    [3. 5.]
    >>> families = [ [0], [1], [2] ]  # three singleton families
    >>> v = [[3,0,0],[0,4,0],[5,5,5]]
    >>> print(leximin_optimal_allocation_for_families(v,families).round(3).utility_profile())
    [3. 4. 5.]
    >>> families = [ [0, 1], [2] ]  
    >>> print(leximin_optimal_allocation_for_families(v,families).round(3).utility_profile())
    [3. 4. 5.]
    >>> families = [ [0], [1,2] ]  
    >>> print(leximin_optimal_allocation_for_families(v,families).round(3).utility_profile())
    [ 3.  4. 10.]
    """
    v = ValuationMatrix(instance)
    num_of_objects  = v.num_of_objects
    num_of_agents   = v.num_of_agents
    num_of_families = len(families)
    agent_to_family = map_agent_to_family(families, num_of_agents)
    logger.info("map_agent_to_family = %s",agent_to_family)
    allocation_vars = cvxpy.Variable((num_of_families, num_of_objects))
    feasibility_constraints = [
        sum([allocation_vars[f][o] for f in range(num_of_families)])==1
        for o in range(num_of_objects)
    ]
    positivity_constraints = [
        allocation_vars[f][o] >= 0 for f in range(num_of_families)
        for o in range(num_of_objects)
    ]
    utilities = [sum([allocation_vars[agent_to_family[i]][o]*v[i][o] 
        for o in range(num_of_objects)]) 
        for i in range(num_of_agents)]
    leximin_solve(objectives=utilities, constraints=feasibility_constraints+positivity_constraints)
    allocation_matrix = allocation_vars.value

    return AllocationToFamilies(v, allocation_matrix, families)
Exemple #12
0
 def is_single_proportional(self, valuation_matrix, x: int) -> bool:
     """
     Checks if this graph can possibly correspond to a proportional allocation for a single agent x.
     for specific i and any j : ui(xi)>=1/n(xi)
     :param valuation_matrix represents the agents' valuations.
     :param x the index of agent we check
     :return: bool value if the allocation is proportional
     >>> g = ConsumptionGraph([[1,1,0,0],[1,1,0,1]])
     >>> v = [[1,3,5,2],[4,3,2,4]]
     >>> g.is_single_proportional(v,0)
     False
     >>> g.is_single_proportional(v,1)
     True
     >>> g = ConsumptionGraph([[1, 0.0, 0.0], [0.0, 1, 1], [0.0, 0.0, 0.0]])
     >>> v = [[1,3,5],[4,3,2],[4,3,2]]
     >>> g.is_single_proportional(v,0)
     False
     >>> g.is_single_proportional(v,1)
     True
     >>> g.is_single_proportional(v,2)
     False
     >>> g = ConsumptionGraph([[1, 1, 1], [0.0, 1, 1], [0.0, 0.0, 1]])
     >>> v = [[1,3,5],[4,3,2],[4,3,2]]
     >>> g.is_single_proportional(v,0)
     True
     >>> g.is_single_proportional(v,1)
     True
     >>> g.is_single_proportional(v,2)
     False
     >>> g = ConsumptionGraph([[0.0, 0.0, 1], [0.0, 1, 0.0], [0.0, 0.0, 1]])
     >>> v = [[1,3,5],[4,1,2],[4,3,2]]
     >>> g.is_single_proportional(v,0)
     True
     >>> g.is_single_proportional(v,1)
     False
     >>> g.is_single_proportional(v,2)
     False
     """
     valuation_matrix = ValuationMatrix(valuation_matrix)
     sum = 0
     part = 0
     for i in range(0, self.num_of_objects):
         sum += valuation_matrix[x][i]
         part += valuation_matrix[x][i] * self.__graph[x][i]
     sum = sum / valuation_matrix.num_of_agents
     return part >= sum
Exemple #13
0
def solve(agents) -> List[List[int]]:
    """
    recursive function which takes valuations and returns a PROPm allocation
    as a list of bundles
    >>> import numpy as np
    >>> v = np.array([
    ... [0.25, 0.25, 0.25, 0.25, 0, 0],
    ... [0.25, 0, 0.26, 0, 0.25, 0.24],
    ... [0.25, 0, 0.24, 0, 0.25, 0.26]
    ... ])
    >>> solve(v)
    [[2, 3], [1, 5], [4, 0]]
    >>> solve(v[np.ix_([0, 1, 2], [0, 2, 1, 3, 4, 5])])
    [[2, 3], [0, 1], [4, 5]]
    """
    v = ValuationMatrix(agents)
    if v.num_of_agents == 0 or v.num_of_objects == 0:
        return []

    logger.info("Looking for PROPm allocation for %d agents and %d items", 
        v.num_of_agents, v.num_of_objects)
    logger.info("Solving a problem defined by valuation matrix\n %s", str(np.array(agents)))

    total_value = v.normalize()

    for agent in v.agents():
        for item in v.objects():
            if v[agent][item] * v.num_of_agents > total_value:
                logger.info("Allocating item %d to agent %d as she values it as %f > 1/n", item, agent,
                            v[agent][item] / total_value)

                allocation = solve(v.without_agent(agent).without_object(item))
                insert_agent_into_allocation(agent, item, allocation)
                return allocation

    bundles = divide(v)
    logger.info("Divider divides items into following bundles: %s", str(bundles))

    remaining_agents = set(range(1, v.num_of_agents))

    logger.info("Building decomposition:")
    decomposition = Decomposition(v)
    for t in range(1, v.num_of_agents + 1):
        considered_items = sum(bundles[:t], [])

        candidates = list(
            filter(lambda a: v.num_of_agents * v.agent_value_for_bundle(a, considered_items) > t * total_value,
                   remaining_agents))
        logger.info("There are %d remaining agents that prefer sharing first %d bundles rather than last %d: %s",
                    len(candidates), str(candidates), t, v.num_of_agents - t)

        while len(candidates) > 0 and decomposition.num_of_agents() < t:
            logger.info("Current decomposition:\n %s", str(decomposition))

            decomposition.update(candidates[0], bundles[t - 1])

            remaining_agents = set(range(1, v.num_of_agents)).difference(decomposition.get_all_agents())
            candidates = list(filter(
                lambda a: v.num_of_agents * v.agent_value_for_bundle(a, considered_items) > t * total_value,
                remaining_agents))

        if decomposition.num_of_agents() < t:
            decomposition.agents.append(remaining_agents)
            decomposition.bundles.append(sum(bundles[t:], []))
            logger.info("Final decomposition:\n %s", str(decomposition))

            logger.info("Allocating bundle %d to divider agent", t)
            allocation = list([[] for _ in range(v.num_of_agents)])
            allocation[0] = bundles[t - 1]

            for agents, bundle in zip(decomposition.agents, decomposition.bundles):
                agents = list(sorted(agents))
                sub_problem = v.submatrix(agents, bundle)
                solution = solve(sub_problem)
                for i, agent in enumerate(agents):
                    for j in solution[i]:
                        allocation[agent].append(bundle[j])

            return allocation
Exemple #14
0
    def adapted_algorithm(input, *args, **kwargs):

        # Step 1. Adapt the input:
        valuation_matrix = list_of_valuations = object_names = agent_names = None
        if isinstance(
                input,
                ValuationMatrix):  # instance is already a valuation matrix
            valuation_matrix = input
        elif isinstance(input,
                        np.ndarray):  # instance is a numpy valuation matrix
            valuation_matrix = ValuationMatrix(input)
        elif isinstance(input, list) and isinstance(input[0],
                                                    list):  # list of lists
            list_of_valuations = input
            valuation_matrix = ValuationMatrix(list_of_valuations)
        elif isinstance(input, dict):
            agent_names = list(input.keys())
            list_of_valuations = list(input.values())
            if isinstance(list_of_valuations[0],
                          dict):  # maps agent names to dicts of valuations
                object_names = list(list_of_valuations[0].keys())
                list_of_valuations = [[
                    valuation[object] for object in object_names
                ] for valuation in list_of_valuations]
            valuation_matrix = ValuationMatrix(list_of_valuations)
        else:
            raise TypeError(f"Unsupported input type: {type(input)}")

        # Step 2. Run the algorithm:
        output = algorithm(valuation_matrix, *args, **kwargs)
        # return output if isinstance(output,Allocation) else Allocation(valuation_matrix, output)

        # Step 3. Adapt the output:
        if isinstance(output, Allocation):
            return output

        if agent_names is None:
            agent_names = [f"Agent #{i}" for i in valuation_matrix.agents()]

        # print("agent_names", agent_names, "object_names", object_names,"output", output)

        if isinstance(output, np.ndarray) or isinstance(
                output, AllocationMatrix):  # allocation matrix
            allocation_matrix = AllocationMatrix(output)
            if isinstance(input, dict):
                list_of_bundles = [
                    FractionalBundle(allocation_matrix[i], object_names)
                    for i in allocation_matrix.agents()
                ]
                dict_of_bundles = dict(zip(agent_names, list_of_bundles))
                return Allocation(input,
                                  dict_of_bundles,
                                  matrix=allocation_matrix)
            else:
                return Allocation(valuation_matrix, allocation_matrix)
        elif isinstance(output, list):
            if object_names is None:
                list_of_bundles = output
            else:
                list_of_bundles = [[
                    object_names[object_index] for object_index in bundle
                ] for bundle in output]
            dict_of_bundles = dict(zip(agent_names, list_of_bundles))
            return Allocation(
                input if isinstance(input, dict) else valuation_matrix,
                dict_of_bundles)
        else:
            raise TypeError(f"Unsupported output type: {type(output)}")
Exemple #15
0
 def __init__(self, valuation_matrix):
     valuation_matrix = ValuationMatrix(valuation_matrix)
     self.valuation_matrix = valuation_matrix
     self.all_ratios = compute_all_ratios(valuation_matrix)
Exemple #16
0
def dominating_allocation_with_bounded_sharing(instance: Any,
                                               thresholds: List) -> Allocation:
    """
    Finds an allocation in which each agent i gets value at least thresholds[i],
    and has at most n-1 sharings, where n is the number of agents.

    IDEA: find a Basic Feasible Solution (BFS) of a linear program.
    NOTE: some solvers return a BFS by default (particularly, those running Simplex).

    >>> logger.setLevel(logging.WARNING)
    >>> instance = [[8,2],[5,5]]
    >>> dominating_allocation_with_bounded_sharing(instance, thresholds=[0,0]).round(3)
    Agent #0 gets {} with value 0.
    Agent #1 gets { 100.0% of 0, 100.0% of 1} with value 10.
    <BLANKLINE>
    >>> dominating_allocation_with_bounded_sharing(instance, thresholds=[1,1]).round(3)
    Agent #0 gets { 12.5% of 0} with value 1.
    Agent #1 gets { 87.5% of 0, 100.0% of 1} with value 9.38.
    <BLANKLINE>
    >>> dominating_allocation_with_bounded_sharing(instance, thresholds=[2,2]).round(3)
    Agent #0 gets { 25.0% of 0} with value 2.
    Agent #1 gets { 75.0% of 0, 100.0% of 1} with value 8.75.
    <BLANKLINE>
    >>> dominating_allocation_with_bounded_sharing(instance, thresholds=[5,5]).round(3)
    Agent #0 gets { 62.5% of 0} with value 5.
    Agent #1 gets { 37.5% of 0, 100.0% of 1} with value 6.88.
    <BLANKLINE>
    """
    # logger.info("Finding an allocation with thresholds %s", thresholds)
    v = ValuationMatrix(instance)
    allocation_vars = cvxpy.Variable((v.num_of_agents, v.num_of_objects))
    feasibility_constraints = [
        sum([allocation_vars[i][o] for i in v.agents()]) == 1
        for o in v.objects()
    ]
    positivity_constraints = [
        allocation_vars[i][o] >= 0 for i in v.agents() for o in v.objects()
    ]
    utilities = [
        sum([allocation_vars[i][o] * v[i][o] for o in v.objects()])
        for i in v.agents()
    ]
    utility_constraints = [
        utilities[i] >= thresholds[i] for i in range(v.num_of_agents - 1)
    ]
    constraints = feasibility_constraints + positivity_constraints + utility_constraints
    problem = cvxpy.Problem(cvxpy.Maximize(utilities[v.num_of_agents - 1]),
                            constraints)
    logger.info("constraints: %s", constraints)
    solvers = [
        (cvxpy.SCIPY, {
            'method': 'highs-ds'
        }),  # Always finds a BFS
        (cvxpy.MOSEK, {
            "bfs": True
        }),  # Always finds a BFS
        (cvxpy.OSQP, {}),  # Default - not sure it returns a BFS
        (cvxpy.SCIPY, {}),  # Default - not sure it returns a BFS
    ]
    solve(problem, solvers=solvers)
    if problem.status == "optimal":
        allocation_matrix = allocation_vars.value
        return allocation_matrix
    else:
        raise cvxpy.SolverError(
            f"No optimal solution found: status is {problem.status}")
Exemple #17
0
    valuation_matrix = instance
    num_agents = valuation_matrix.num_of_agents
    num_objects = valuation_matrix.num_of_objects
    bundles = np.zeros([num_agents, num_objects])
    i_agent = first_agent
    for i_object in range(num_objects):
        bundles[i_agent][i_object] = 1
        i_agent += 1
        if i_agent >= num_agents:
            i_agent = 0
    return bundles


#' Native calls (works even without the decorator):
print(
    dummy_matrix_matrix_algorithm(ValuationMatrix([[11, 22, 33],
                                                   [44, 55, 66]])).matrix)
print(
    dummy_matrix_matrix_algorithm(ValuationMatrix([[11, 22, 33], [44, 55,
                                                                  66]]),
                                  first_agent=1).matrix)

#' Calls with a list of lists:
print(dummy_matrix_matrix_algorithm([[11, 22, 33], [44, 55, 66]]))
print(
    dummy_matrix_matrix_algorithm([[11, 22, 33], [44, 55, 66]], first_agent=1))

#' Call with a dict mapping agent names to lists of values:
input_dict_of_dicts = {"a": [11, 22, 33], "b": [44, 55, 66]}
print(dummy_matrix_matrix_algorithm(input_dict_of_dicts))
print(dummy_matrix_matrix_algorithm(input_dict_of_dicts, first_agent=1))
Exemple #18
0
 def __init__(self, values: ValuationMatrix):
     self.v = values
     self.total_value = values.verify_normalized()
     self.agents = []
     self.bundles = []
Exemple #19
0
class Bag:
	"""
	represents a bag for objects. 
	Different agents may have different valuations for the objects.
	>>> values = [[11,33],[44,22]]
	>>> thresholds = [30,30]
	>>> bag = Bag(values, thresholds)
	>>> print(bag)
	Bag objects: [], values: [0. 0.]
	>>> print(bag.willing_agent([0,1]))
	None
	>>> bag.append(0)
	>>> print(bag)
	Bag objects: [0], values: [11. 44.]
	>>> print(bag.willing_agent([0,1]))
	1
	>>> bag.append(1)
	>>> print(bag)
	Bag objects: [0, 1], values: [44. 66.]
	>>> print(bag.willing_agent([0,1]))
	0
	>>> bag.reset()
	>>> print(bag)
	Bag objects: [], values: [0. 0.]
	>>> bag.append([0,1])
	>>> print(bag)
	Bag objects: [0, 1], values: [44. 66.]
	"""

	def __init__(self, values:ValuationMatrix, thresholds:List[float]):
		"""
		Initialize an empty bag.
		:param values: a matrix representing additive valuations (a row for each agent, a column for each object).
		:param thresholds: determines, for each agent, the minimum value that should be in a bag before the agent accepts it.
		"""
		self.values = ValuationMatrix(values)
		self.thresholds = thresholds
		self.reset()

	def reset(self): 
		"""
		Empty the bag.
		"""
		self.objects = []
		self.map_agent_to_bag_value = np.zeros(self.values.num_of_agents)
		logger.info("Starting an empty bag. %d agents and %d objects.", self.values.num_of_agents, self.values.num_of_objects)

	def append(self, object:int):
		"""
		Append the given object or objects to the bag, and update the agents' valuations accordingly.
		"""
		if isinstance(object,list):
			for o in object:
				self.append(o)
			return
		logger.info("   Appending object %s.", object)
		self.objects.append(object)
		for agent in self.values.agents():
			self.map_agent_to_bag_value[agent] += self.values[agent][object]
		logger.debug("      Bag values: %s.", self.map_agent_to_bag_value)

	def willing_agent(self, remaining_agents)->int:
		"""
		:return the index of an arbitrary agent, from the list of remaining agents, who is willing to accept the bag 
		 (i.e., the bag's value is above the agent's threshold).
		 If no remaining agent is willing to accept the bag, None is returned.
		"""
		for agent in remaining_agents:
			logger.debug("      Checking if agent %d is willing to take the bag", agent)
			if self.map_agent_to_bag_value[agent] >= self.thresholds[agent]:
				return agent
		return None


	def fill(self, remaining_objects, remaining_agents)->(int, list):
		"""
		Fill the bag with objects until at least one agent is willing to accept it.
		:return the willing agent, or None if the objects are insufficient.
		>>> bag = Bag(values=[[1,2,3,4,5,6],[6,5,4,3,2,1]], thresholds=[10,10])
		>>> remaining_objects = list(range(6))
		>>> remaining_agents = [0,1]
		>>> (willing_agent, allocated_objects) = bag.fill(remaining_objects, remaining_agents)
		>>> willing_agent
		1
		>>> allocated_objects
		[0, 1]
		>>> remaining_objects = list(set(remaining_objects) - set(allocated_objects))
		>>> bag = Bag(values=[[1,2,3,4,5,6],[6,5,4,3,2,1]], thresholds=[10,10])
		>>> bag.fill(remaining_objects, remaining_agents)
		(0, [2, 3, 4])
		>>> bag = Bag(values=[[20]], thresholds=[10])  # Edge case: single object
		>>> bag.fill(remaining_objects=[0], remaining_agents=[0])
		(0, [0])
		>>> bag = Bag(values=[[20,5]], thresholds=[10])  # Edge case: bag with an existing large object
		>>> bag.append(0)
		>>> bag.fill(remaining_objects=[1], remaining_agents=[0])
		(0, [0])
		"""
		if len(remaining_agents)==0:
			return (None, None)
		willing_agent = self.willing_agent(remaining_agents)
		if willing_agent is not None:
			return (willing_agent, self.objects)
		for object in remaining_objects:
			self.append(object)
			willing_agent = self.willing_agent(remaining_agents)
			if willing_agent is not None:
				return (willing_agent, self.objects)
		return (None, None)

	def __str__(self):
		return f"Bag objects: {self.objects}, values: {self.map_agent_to_bag_value}"