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)
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
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)
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 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
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
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
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)
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
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)
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
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
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)}")
def __init__(self, valuation_matrix): valuation_matrix = ValuationMatrix(valuation_matrix) self.valuation_matrix = valuation_matrix self.all_ratios = compute_all_ratios(valuation_matrix)
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}")
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))
def __init__(self, values: ValuationMatrix): self.v = values self.total_value = values.verify_normalized() self.agents = [] self.bundles = []
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}"