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)
 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 #3
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 #4
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 #5
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 #6
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 #7
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)
Exemple #8
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 #9
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 #10
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 #11
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 #12
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 #13
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 #14
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 #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}")