From 1b5e2dbcc5fcb4135372bfa8a6cf61813678052e Mon Sep 17 00:00:00 2001 From: MoriyaEster Date: Wed, 8 May 2024 18:06:30 +0300 Subject: [PATCH 01/42] adding our folders --- .../Optimization_based_Mechanisms/OC.py | 36 +++ .../Optimization_based_Mechanisms/SP.py | 115 ++++++++++ .../Optimization_based_Mechanisms/SP_O.py | 35 +++ .../Optimization_based_Mechanisms/TTC.py | 111 ++++++++++ .../Optimization_based_Mechanisms/TTC_O.py | 35 +++ .../Optimization_based_Mechanisms/__init__.py | 6 + .../test_OC.py | 62 ++++++ .../test_SP.py | 199 +++++++++++++++++ .../test_SP_O.py | 67 ++++++ .../test_TTC.py | 209 ++++++++++++++++++ .../test_TTC_O.py | 95 ++++++++ 11 files changed, 970 insertions(+) create mode 100644 fairpyx/algorithms/Optimization_based_Mechanisms/OC.py create mode 100644 fairpyx/algorithms/Optimization_based_Mechanisms/SP.py create mode 100644 fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py create mode 100644 fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py create mode 100644 fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py create mode 100644 fairpyx/algorithms/Optimization_based_Mechanisms/__init__.py create mode 100644 tests/Tests_for_Optimization-based_Mechanisms/test_OC.py create mode 100644 tests/Tests_for_Optimization-based_Mechanisms/test_SP.py create mode 100644 tests/Tests_for_Optimization-based_Mechanisms/test_SP_O.py create mode 100644 tests/Tests_for_Optimization-based_Mechanisms/test_TTC.py create mode 100644 tests/Tests_for_Optimization-based_Mechanisms/test_TTC_O.py diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py b/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py new file mode 100644 index 0000000..e91221f --- /dev/null +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py @@ -0,0 +1,36 @@ +""" + "Optimization-based Mechanisms for the Course Allocation Problem", by Hoda Atef Yekta, Robert Day (2020) + https://doi.org/10.1287/ijoc.2018.0849 + + Programmer: Tamar Bar-Ilan, Moriya Ester Ohayon, Ofek Kats +""" + +from fairpyx import Instance, AllocationBuilder +import logging +logger = logging.getLogger(__name__) + +def OC_function(alloc: AllocationBuilder): + """ + Algorethem 5: Allocate the given items to the given agents using the OC protocol. + + in the OC algorithm for CAP, we maximize ordinal utility followed by maximizing cardinal utility among rank-maximal + solutions, performing this two-part optimization once for the whole market. + + :param alloc: an allocation builder, which tracks the allocation and the remaining capacity for items and agents of + the fair course allocation problem(CAP). + + >>> from fairpyx.adaptors import divide + >>> s1 = {"c1": 44, "c2": 39, "c3": 17} + >>> s2 = {"c1": 50, "c2": 45, "c3": 5} + >>> agent_capacities = {"s1": 2, "s2": 2} # 4 seats required + >>> course_capacities = {"c1": 2, "c2": 1, "c3": 2} # 5 seats available + >>> valuations = {"s1": s1, "s2": s2} + >>> instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) + >>> divide(OC_function, instance=instance) + {'s1': ['c1', 'c3'], 's2': ['c1', 'c2']} + """ + +if __name__ == "__main__": + import doctest, sys + print(doctest.testmod()) + diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py b/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py new file mode 100644 index 0000000..2d81333 --- /dev/null +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py @@ -0,0 +1,115 @@ +""" + "Optimization-based Mechanisms for the Course Allocation Problem", by Hoda Atef Yekta, Robert Day (2020) + https://doi.org/10.1287/ijoc.2018.0849 + + Programmer: Tamar Bar-Ilan, Moriya Ester Ohayon, Ofek Kats +""" +from fairpyx import Instance, AllocationBuilder, ExplanationLogger +import logging +logger = logging.getLogger(__name__) + +def SP_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger = ExplanationLogger()): + """ + Algorethem 2: Allocate the given items to the given agents using the SP protocol. + + SP (Second Price) in each round distributes one course to each student, with the refund of the bids according to the price of the course. + + :param alloc: an allocation builder, which tracks the allocation and the remaining capacity for items and agents of + the fair course allocation problem(CAP). + + + >>> from fairpyx.adaptors import divide + >>> s1 = {"c1": 50, "c2": 49, "c3": 1} + >>> s2 = {"c1": 48, "c2": 46, "c3": 6} + >>> agent_capacities = {"s1": 1, "s2": 1} # 2 seats required + >>> course_capacities = {"c1": 1, "c2": 1, "c3": 1} # 3 seats available + >>> valuations = {"s1": s1, "s2": s2} + >>> instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) + >>> divide(SP_function, instance=instance) + {'s1': ['c1'], 's2': ['c2']} + """ + + explanation_logger.info("\nAlgorithm SP starts.\n") + + agent_order = [] # list of all the instances of agents as many as their items capacities (from the article: sum of Ki) + map_agent_to_best_item = {} # dict of the max bids for each agent in specific iteration (from the article: max{i, b}) + map_course_to_students_with_max_bids = {c: {} for c in alloc.remaining_item_capacities} + + for agent, capacity in alloc.remaining_agent_capacities.items(): # save the agent number of his needed courses (שומר את הסטודנט מספר פעמים כמספר הקורסים שהוא צריך) + agent_order.extend([agent] * capacity) + + set_agent = list(dict.fromkeys(agent_order)) # set of the agents by thier order . each agent appears only once + + dict_extra_bids = {s: 0 for s in set_agent} + + for agent in agent_order: + if agent not in alloc.remaining_agent_capacities: # to check if not all the agents got k courses + continue + + while set_agent: # round i + for current_agent in set_agent: # check the course with the max bids for each agent in the set (who didnt get a course in this round) + if len(alloc.remaining_agent_capacities) == 0 or len( + alloc.remaining_item_capacities) == 0: # check if all the agents got their courses or there are no more + set_agent = [] + break + + potential_items_for_agent = set(alloc.remaining_items()).difference(alloc.bundles[current_agent]) # set of all the courses that have places sub the courses the agent got (under the assumption that if agent doesnt want a course the course got 0 bids automaticlly) + if len(potential_items_for_agent) == 0: + logger.info("Agent %s cannot pick any more items: remaining=%s, bundle=%s", current_agent, + alloc.remaining_item_capacities, alloc.bundles[current_agent]) + if current_agent in agent_order and current_agent in alloc.remaining_agent_capacities: # checking if the agent in dict before removing + alloc.remove_agent(current_agent) + agent_order.remove(current_agent) + continue + + map_agent_to_best_item[current_agent] = max(potential_items_for_agent, + key=lambda item: alloc.effective_value(current_agent, + item)) # for each agent save the course with the max bids from the potential courses only + # update bids for all student in dict_extra_bids + for s in dict_extra_bids: + if s in map_agent_to_best_item: + dict_extra_bids[s] += alloc.effective_value(s ,map_agent_to_best_item[s]) + + #create dict for each course of student that point on the course and their bids + map_course_to_students_with_max_bids = {course: + {student: dict_extra_bids[student] for student in map_agent_to_best_item if map_agent_to_best_item[student] == course} + for course in alloc.remaining_items() + } + # for c in map_course_to_students_with_bids: + # map_course_to_students_with_bids[c] = {s: dict_extra_bids[s] for s in map_agent_to_best_item if map_agent_to_best_item[s] == c} + # for s in map_agent_to_best_item: + # if map_agent_to_best_item[s] == c: + # dict_s_to_c[s] = dict_extra_bids[s] + # dict_by_course[c] = dict_s_to_c + + #loop on the dict_by_course and for each curse give it to as many studens it can + for course in map_course_to_students_with_max_bids: + if course in alloc.remaining_item_capacities: + c_capacity = alloc.remaining_item_capacities[course] + if len(map_course_to_students_with_max_bids[course]) == 0: + continue + elif len(map_course_to_students_with_max_bids[course]) <= c_capacity: + for s in map_course_to_students_with_max_bids[course]: + alloc.give(s,course,logger) + set_agent.remove(s) # removing the agent from the set (dont worry he will come back in the next round) + if s in map_agent_to_best_item: # removing the agent from the max dict + del map_agent_to_best_item[s] + #del dict_by_course[c][s] + else: + sorted_students = sorted(map_course_to_students_with_max_bids[course].keys(), key=lambda name: map_course_to_students_with_max_bids[course][name], reverse=True) #sort the keys by their values (descending order) + price = dict_extra_bids[sorted_students[c_capacity]] #the amount of bids of the first studen that cant get the course + for i in range(c_capacity): + s = sorted_students[i] + alloc.give(s, course, logger) + set_agent.remove(s) # removing the agent from the set (dont worry he will come back in the next round) + if s in map_agent_to_best_item: # removing the agent from the max dict + del map_agent_to_best_item[s] # removing the agent from the max dict + del map_course_to_students_with_max_bids[course][s] + dict_extra_bids[s] -= price + + set_agent = list(dict.fromkeys(alloc.remaining_agents())) #with this random in loop and k passes + #set_agent = list(dict.fromkeys(agent_order)) #with this the random and k diffrent got ValueError + +if __name__ == "__main__": + import doctest, sys + print(doctest.testmod()) \ No newline at end of file diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py new file mode 100644 index 0000000..7ff2062 --- /dev/null +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py @@ -0,0 +1,35 @@ +""" + "Optimization-based Mechanisms for the Course Allocation Problem", by Hoda Atef Yekta, Robert Day (2020) + https://doi.org/10.1287/ijoc.2018.0849 + + Programmer: Tamar Bar-Ilan, Moriya Ester Ohayon, Ofek Kats +""" +from fairpyx import Instance, AllocationBuilder +import logging +logger = logging.getLogger(__name__) + +def SP_O_function(alloc: AllocationBuilder): + """ + Algorethem 4: Allocate the given items to the given agents using the SP-O protocol. + + SP-O in each round distributes one course to each student, with the refund of the bids according to the price of + the course. Uses linear planning for optimality. + + :param alloc: an allocation builder, which tracks the allocation and the remaining capacity for items and agents of + the fair course allocation problem(CAP). + + + >>> from fairpyx.adaptors import divide + >>> s1 = {"c1": 50, "c2": 49, "c3": 1} + >>> s2 = {"c1": 48, "c2": 46, "c3": 6} + >>> agent_capacities = {"s1": 1, "s2": 1} # 2 seats required + >>> course_capacities = {"c1": 1, "c2": 1, "c3": 1} # 3 seats available + >>> valuations = {"s1": s1, "s2": s2} + >>> instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) + >>> divide(SP_O_function, instance=instance) + {'s1': ['c2'], 's2': ['c1']} + """ + +if __name__ == "__main__": + import doctest, sys + print(doctest.testmod()) \ No newline at end of file diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py new file mode 100644 index 0000000..e7adbdb --- /dev/null +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py @@ -0,0 +1,111 @@ +""" + "Optimization-based Mechanisms for the Course Allocation Problem", by Hoda Atef Yekta, Robert Day (2020) + https://doi.org/10.1287/ijoc.2018.0849 + + Programmer: Tamar Bar-Ilan, Moriya Ester Ohayon, Ofek Kats +""" + +from fairpyx import Instance, AllocationBuilder, ExplanationLogger +from itertools import cycle + +import logging +logger = logging.getLogger(__name__) + +def map_agent_to_best_item(alloc:AllocationBuilder): + pass + +def TTC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger = ExplanationLogger()): + """ + Algorithm: Allocate the given items to the given agents using the TTC protocol. + + TTC (top trading-cycle) assigns one course in each round to each student, the winning students are defined based on the students’ bid values. + + :param alloc: an allocation builder, which tracks the allocation and the remaining capacity for items and agents of the fair course allocation problem(CAP). + + >>> from fairpyx.adaptors import divide + >>> s1 = {"c1": 50, "c2": 49, "c3": 1} + >>> s2 = {"c1": 48, "c2": 46, "c3": 6} + >>> agent_capacities = {"s1": 1, "s2": 1} # 2 seats required + >>> course_capacities = {"c1": 1, "c2": 1, "c3": 1} # 3 seats available + >>> valuations = {"s1": s1, "s2": s2} + >>> instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) + >>> divide(TTC_function, instance=instance) + {'s1': ['c1'], 's2': ['c2']} + """ + explanation_logger.info("\nAlgorithm TTC starts.\n") + + agent_order = [] # list of all the instances of agents as many as their items capacities (from the article: sum of Ki) + map_agent_to_best_item = {} # dict of the max bids for each agent in specific iteration (from the article: max{i, b}) + + # for agent, capacity in alloc.remaining_agent_capacities.items(): # save the agent number of his needed courses (שומר את הסטודנט מספר פעמים כמספר הקורסים שהוא צריך) + # agent_order.extend([agent] * capacity) + # + # agents_who_need_an_item_in_current_iteration = list(dict.fromkeys(agent_order)) # set of the agents by thier order . each agent appears only once + + max_iterations = max(alloc.remaining_agent_capacities[agent] for agent in alloc.remaining_agents()) + for iteration in range(max_iterations): # External loop of algorithm: in each iteration, each student gets 1 seat (if possible). + if len(alloc.remaining_agent_capacities) == 0 or len(alloc.remaining_item_capacities) == 0: # check if all the agents got their courses or there are no more + break + agents_who_need_an_item_in_current_iteration = set(alloc.remaining_agents()) + while agents_who_need_an_item_in_current_iteration: # round i + # 1. Create the dict map_agent_to_best_item + agents_with_no_potential_items = set() + for current_agent in agents_who_need_an_item_in_current_iteration: # check the course with the max bids for each agent in the set (who didnt get a course in this round) + potential_items_for_agent = set(alloc.remaining_items()).difference(alloc.bundles[current_agent]) # set of all the courses that have places sub the courses the agent got (under the assumption that if agent doesnt want a course the course got 0 bids automaticlly) + # potential_items_for_agent = alloc.remaining_items_for_agent(current_agent) # set of all the courses that have places sub the courses the agent got (under the assumption that if agent doesnt want a course the course got 0 bids automaticlly) + if len(potential_items_for_agent) == 0: + agents_with_no_potential_items.add(current_agent) + else: + map_agent_to_best_item[current_agent] = max(potential_items_for_agent, + key=lambda item: alloc.effective_value(current_agent, item)) # for each agent save the course with the max bids from the potential courses only + + for current_agent in agents_with_no_potential_items: + logger.info("Agent %s cannot pick any more items: remaining=%s, bundle=%s", current_agent, + alloc.remaining_item_capacities, alloc.bundles[current_agent]) + alloc.remove_agent(current_agent) + agents_who_need_an_item_in_current_iteration.remove(current_agent) + + # 2. Allocate the remaining seats in each course: + for course in list(alloc.remaining_items()): + sorted_students_pointing_to_course = sorted( + [student for student in map_agent_to_best_item if map_agent_to_best_item[student] == course], + key=lambda student: alloc.effective_value(student,course), + reverse=True) # sort the keys by their values (descending order) + remaining_capacity = alloc.remaining_item_capacities[course] + sorted_students_who_can_get_course = sorted_students_pointing_to_course[:remaining_capacity] + for student in sorted_students_who_can_get_course: + alloc.give(student, course, logger) + agents_who_need_an_item_in_current_iteration.remove(student) # removing the agent from the set (dont worry he will come back in the next round) + map_agent_to_best_item.pop(student, None) # Delete student if exists in the dict + + + + + # max_val = 0 + # student_with_max_bids = None + # for agent,best_item in map_agent_to_best_item.items(): # checking the agent with the most bids from agents_with_max_item + # if alloc.effective_value(agent,best_item) > max_val: + # max_val = alloc.effective_value(agent,best_item) + # student_with_max_bids = agent + # + # # if max_val == 0: # if agent bids 0 on item he won't get that item + # # return + # if student_with_max_bids is not None and student_with_max_bids in alloc.remaining_agent_capacities: #after we found the agent we want to give him the course, checking if the agent in dict before removing + # if alloc.remaining_agent_capacities[student_with_max_bids] > 0: + # alloc.give(student_with_max_bids, map_agent_to_best_item[student_with_max_bids], logger) #the agent gets the course + # agents_who_need_an_item_in_current_iteration.remove(student_with_max_bids) # removing the agent from the set (dont worry he will come back in the next round) + # if student_with_max_bids in map_agent_to_best_item: # removing the agent from the max dict + # del map_agent_to_best_item[student_with_max_bids] + # else: + # agents_who_need_an_item_in_current_iteration = () + + #agents_who_need_an_item_in_current_iteration = list(dict.fromkeys(agent_order)) # adding all the agents for the next round + agents_who_need_an_item_in_current_iteration = list(dict.fromkeys(alloc.remaining_agents())) + + +if __name__ == "__main__": + import doctest + + print("\n", doctest.testmod(), "\n") + + diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py new file mode 100644 index 0000000..b29688f --- /dev/null +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py @@ -0,0 +1,35 @@ +""" + "Optimization-based Mechanisms for the Course Allocation Problem", by Hoda Atef Yekta, Robert Day (2020) + https://doi.org/10.1287/ijoc.2018.0849 + + Programmer: Tamar Bar-Ilan, Moriya Ester Ohayon, Ofek Kats +""" +from fairpyx import Instance, AllocationBuilder +import logging +logger = logging.getLogger(__name__) + +def TTC_O_function(alloc: AllocationBuilder): + """ + Algorethem 3: Allocate the given items to the given agents using the TTC-O protocol. + + TTC-O assigns one course in each round to each student, the winning students are defined based on + the students’ bid values. Uses linear planning for optimality. + + :param alloc: an allocation builder, which tracks the allocation and the remaining capacity for items and agents of + the fair course allocation problem(CAP). + + + >>> from fairpyx.adaptors import divide + >>> s1 = {"c1": 50, "c2": 49, "c3": 1} + >>> s2 = {"c1": 48, "c2": 46, "c3": 6} + >>> agent_capacities = {"s1": 1, "s2": 1} # 2 seats required + >>> course_capacities = {"c1": 1, "c2": 1, "c3": 1} # 3 seats available + >>> valuations = {"s1": s1, "s2": s2} + >>> instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) + >>> divide(TTC_O_function, instance=instance) + {'s1': ['c2'], 's2': ['c1']} + """ + +if __name__ == "__main__": + import doctest, sys + print(doctest.testmod()) \ No newline at end of file diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/__init__.py b/fairpyx/algorithms/Optimization_based_Mechanisms/__init__.py new file mode 100644 index 0000000..5dc463b --- /dev/null +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/__init__.py @@ -0,0 +1,6 @@ +from fairpyx.algorithms.Optimization_based_Mechanisms.OC import OC_function +from fairpyx.algorithms.Optimization_based_Mechanisms.SP_O import SP_O_function +from fairpyx.algorithms.Optimization_based_Mechanisms.SP import SP_function +from fairpyx.algorithms.Optimization_based_Mechanisms.TTC_O import TTC_O_function +from fairpyx.algorithms.Optimization_based_Mechanisms.TTC import TTC_function + diff --git a/tests/Tests_for_Optimization-based_Mechanisms/test_OC.py b/tests/Tests_for_Optimization-based_Mechanisms/test_OC.py new file mode 100644 index 0000000..77c9bb3 --- /dev/null +++ b/tests/Tests_for_Optimization-based_Mechanisms/test_OC.py @@ -0,0 +1,62 @@ +""" +Test that course-allocation algorithms return a feasible solution. + +Programmer: OFEK, TAMAR, MORIYA ESTER +Since: 2024-03 +""" + +import pytest + +import fairpyx +import numpy as np + +NUM_OF_RANDOM_INSTANCES=10 + +def test_optimal_number_of_courses(): + s1 = {"c1": 25, "c2": 25, "c3": 25, "c4": 25} + s2 = {"c1": 20, "c2": 20, "c3": 40, "c4": 20} + instance = fairpyx.Instance( + agent_capacities={"s1": 3, "s2": 3}, + item_capacities={"c1": 1, "c2": 2, "c3": 2, "c4": 1}, + valuations={"s1": s1, "s2": s2} + ) + assert fairpyx.divide(fairpyx.algorithms.OC_function, instance=instance) == {'s1': ['c1', 'c2', 'c3'], 's2': ['c2', 'c3', 'c4']}, "ERROR" + +def test_optimal_change_result(): + s1 = {"c1": 50, "c2": 49, "c3": 1} + s2 = {"c1": 48, "c2": 46, "c3": 6} + instance = fairpyx.Instance( + agent_capacities={"s1": 1, "s2": 1, "s3": 1}, + item_capacities={"c1": 1, "c2": 1, "c3": 1}, + valuations={"s1": s1, "s2": s2} + ) + + assert fairpyx.divide(fairpyx.algorithms.OC_function, instance=instance) == {'s1': ['c2'], 's2': ['c1']}, "ERROR" + + +def test_student_bids_the_same_for_different_courses(): + s1 = {"c1": 44, "c2": 39, "c3": 17} + s2 = {"c1": 50, "c2": 45, "c3": 5} + instance = fairpyx.Instance( + agent_capacities={"s1": 2, "s2": 2}, + item_capacities={"c1": 2, "c2": 1, "c3": 2}, + valuations={"s1": s1, "s2": s2} + ) + + assert fairpyx.divide(fairpyx.algorithms.OC_function, instance=instance) == {'s1': ['c1', 'c3'], 's2': ['c1', 'c2']}, "ERROR" + +def test_random(): + for i in range(NUM_OF_RANDOM_INSTANCES): + np.random.seed(i) + instance = fairpyx.Instance.random_uniform( + num_of_agents=70, num_of_items=10, normalized_sum_of_values=1000, + agent_capacity_bounds=[2,6], + item_capacity_bounds=[20,40], + item_base_value_bounds=[1,1000], + item_subjective_ratio_bounds=[0.5, 1.5] + ) + allocation = fairpyx.divide(fairpyx.algorithms.OC_function, instance=instance) + fairpyx.validate_allocation(instance, allocation, title=f"Seed {i}, OC_function") + +if __name__ == "__main__": + pytest.main(["-v",__file__]) diff --git a/tests/Tests_for_Optimization-based_Mechanisms/test_SP.py b/tests/Tests_for_Optimization-based_Mechanisms/test_SP.py new file mode 100644 index 0000000..5961ba7 --- /dev/null +++ b/tests/Tests_for_Optimization-based_Mechanisms/test_SP.py @@ -0,0 +1,199 @@ + +""" +Test that course-allocation algorithms return a feasible solution. + +Programmer: OFEK, TAMAR, MORIYA ESTER +Since: 2024-03 +""" + +import pytest + +import fairpyx +import numpy as np + +NUM_OF_RANDOM_INSTANCES=10 + + +def test_number_of_courses_is_not_optimal(): + s1 = {"c1": 25, "c2": 25, "c3": 25, "c4": 25} + s2 = {"c1": 20, "c2": 20, "c3": 40, "c4": 20} + instance = fairpyx.Instance( + agent_capacities={"s1": 3, "s2": 3}, + item_capacities={"c1": 1, "c2": 2, "c3": 2, "c4": 1}, + valuations={"s1": s1, "s2": s2} + ) + assert fairpyx.divide(fairpyx.algorithms.SP_function, instance=instance) == {'s1': ['c1', 'c2', 'c4'], 's2': ['c2', 'c3']}, "ERROR" + + +def test_small_example(): + s1 = {"c1": 50, "c2": 40, "c3": 5, "c4": 5} + s2 = {"c1": 20, "c2": 20, "c3": 30, "c4": 30} + s3 = {"c1": 60, "c2": 30, "c3": 1, "c4": 9} + + instance = fairpyx.Instance( + agent_capacities={"s1": 2, "s2": 2, "s3": 2}, + item_capacities={"c1": 1, "c2": 1, "c3": 2, "c4": 2}, + valuations={"s1": s1, "s2": s2, "s3":s3} + ) + + assert fairpyx.divide(fairpyx.algorithms.SP_function, instance=instance) == {'s1': ['c2', 'c3'], + 's2': ['c3', 'c4'], + 's3': ['c1', 'c4']}, "ERROR" + +def test_big_example(): + s1 = {"c1": 50, "c2": 20, "c3": 10, "c4": 10,"c5": 10} + s2 = {"c1": 2, "c2": 3, "c3": 5, "c4": 30,"c5": 60} + s3 = {"c1": 20, "c2": 30, "c3": 10, "c4": 20,"c5": 20} + s4 = {"c1": 25, "c2": 0, "c3": 25, "c4": 25,"c5": 25} + s5 = {"c1": 5, "c2": 2, "c3": 1, "c4": 2,"c5": 25} + s6 = {"c1": 12, "c2": 14, "c3": 50, "c4": 12,"c5": 12} + s7 = {"c1": 1, "c2": 2, "c3": 2, "c4": 5,"c5": 90} + + instance = fairpyx.Instance( + agent_capacities={"s1": 4, "s2": 4, "s3": 4, "s4": 4, "s5": 4, "s6": 4, "s7": 4}, + item_capacities={"c1": 6, "c2": 6, "c3": 7, "c4": 3, "c5": 6}, + valuations={"s1": s1, "s2": s2, "s3": s3, "s4": s4, "s5": s5, "s6": s6, "s7": s7} + ) + + assert fairpyx.divide(fairpyx.algorithms.SP_function, instance=instance) == {'s1': ['c1', 'c2', 'c3', 'c5'], + 's2': ['c1', 'c3', 'c4', 'c5'], + 's3': ['c1', 'c2', 'c4', 'c5'], + 's4': ['c1', 'c2', 'c3', 'c5'], + 's5': ['c1', 'c2', 'c3', 'c5'], + 's6': ['c1', 'c2', 'c3'], + 's7': ['c2', 'c3', 'c4', 'c5']}, "ERROR" + + +def test_same_order_of_course_selection(): + s1 = {"c1": 10, "c2": 20, "c3": 10, "c4": 50, "c5": 10} + s2 = {"c1": 5, "c2": 30, "c3": 3, "c4": 60, "c5": 2} + s3 = {"c1": 20, "c2": 20, "c3": 25, "c4": 25, "c5": 0} + s4 = {"c1": 25, "c2": 25, "c3": 25, "c4": 25, "c5": 0} + s5 = {"c1": 2, "c2": 5, "c3": 2, "c4": 90, "c5": 1} + s6 = {"c1": 12, "c2": 14, "c3": 12, "c4": 50, "c5": 12} + s7 = {"c1": 7, "c2": 5, "c3": 2, "c4": 90, "c5": 1} + + instance = fairpyx.Instance( + agent_capacities={"s1": 4, "s2": 4, "s3": 4, "s4": 4, "s5": 4, "s6": 4, "s7": 4}, + item_capacities={"c1": 6, "c2": 6, "c3": 7, "c4": 3, "c5": 6}, + valuations={"s1": s1, "s2": s2, "s3": s3, "s4": s4, "s5": s5, "s6": s6, "s7": s7} + ) + + assert fairpyx.divide(fairpyx.algorithms.SP_function, instance=instance) == {'s1': ['c1', 'c2', 'c3', 'c5'], + 's2': ['c1', 'c3', 'c4', 'c5'], + 's3': ['c1', 'c2', 'c3', 'c5'], + 's4': ['c1', 'c2', 'c3', 'c5'], + 's5': ['c1', 'c2', 'c3', 'c4'], + 's6': ['c1', 'c2', 'c3', 'c5'], + 's7': ['c2', 'c3', 'c4', 'c5']}, "ERROR" + + +def test_optimal_change_result(): + s1 = {"c1": 50, "c2": 49, "c3": 1} + s2 = {"c1": 48, "c2": 46, "c3": 6} + instance = fairpyx.Instance( + agent_capacities={"s1": 1, "s2": 1, "s3": 1}, + item_capacities={"c1": 1, "c2": 1, "c3": 1}, + valuations={"s1": s1, "s2": s2} + ) + + assert fairpyx.divide(fairpyx.algorithms.SP_function, instance=instance) == {'s1': ['c1'], 's2': ['c2']}, "ERROR" + + +def test_sub_round_within_round(): + s1 = {"c1": 44, "c2": 39, "c3": 17} + s2 = {"c1": 50, "c2": 45, "c3": 5} + s3 = {"c1": 45, "c2": 40, "c3": 15} + instance = fairpyx.Instance( + agent_capacities={"s1": 1, "s2": 1, "s3": 1}, + item_capacities={"c1": 1, "c2": 1, "c3": 1}, + valuations={"s1": s1, "s2": s2, "s3": s3} + ) + + assert fairpyx.divide(fairpyx.algorithms.SP_function, instance=instance) == {'s1': ['c3'], 's2': ['c1'], 's3': ['c2']}, "ERROR" + + +def test_students_with_the_same_list(): + s1 = {"c1": 55, "c2": 45} + s2 = {"c1": 55, "c2": 45} + instance = fairpyx.Instance( + agent_capacities={"s1": 1, "s2": 1}, + item_capacities={"c1": 1, "c2": 1}, + valuations={"s1": s1, "s2": s2} + ) + + assert fairpyx.divide(fairpyx.algorithms.SP_function, instance=instance) == {'s1': ['c1'], 's2': ['c2']}, "ERROR" + + +def test_sub_round_within_sub_round(): + s1 = {"c1": 40, "c2": 10, "c3": 20, "c4": 30} + s2 = {"c1": 50, "c2": 10, "c3": 15, "c4": 25} + s3 = {"c1": 60, "c2": 30, "c3": 2, "c4": 8} + instance = fairpyx.Instance( + agent_capacities={"s1": 2, "s2": 2, "s3": 2}, + item_capacities={"c1": 1, "c2": 2, "c3": 2, "c4": 1}, + valuations={"s1": s1, "s2": s2, "s3": s3} + ) + + assert fairpyx.divide(fairpyx.algorithms.SP_function, instance=instance) == {'s1': ['c1', 'c2'], 's2': ['c4', 'c3'], 's3': ['c3', 'c2']}, "ERROR" + + +def test_student_bids_the_same_for_different_courses(): + s1 = {"c1": 50, "c2": 50} + s2 = {"c1": 40, "c2": 60} + s3 = {"c1": 71, "c2": 25} + instance = fairpyx.Instance( + agent_capacities={"s1": 1, "s2": 1, "s3": 1}, + item_capacities={"c1": 2, "c2": 1}, + valuations={"s1": s1, "s2": s2, "s3": s3} + ) + + assert fairpyx.divide(fairpyx.algorithms.SP_function, instance=instance) == {'s1': ['c1'], 's2': ['c2'], 's3': ['c1']}, "ERROR" + + +def test_from_the_article(): + s1 = {"c1": 400, "c2": 150, "c3": 230, "c4": 200, "c5": 20} + s2 = {"c1": 245, "c2": 252, "c3": 256, "c4": 246, "c5": 1} + s3 = {"c1": 243, "c2": 230, "c3": 240, "c4": 245, "c5": 42} + s4 = {"c1": 251, "c2": 235, "c3": 242, "c4": 201, "c5": 71} + instance = fairpyx.Instance( + agent_capacities={"s1": 3, "s2": 3, "s3": 3, "s4": 3}, + item_capacities={"c1": 2, "c2": 3, "c3": 3, "c4": 2, "c5": 2}, + item_conflicts={"c1": ['c4'], "c4": ['c1']}, + valuations={"s1": s1, "s2": s2, "s3": s3, "s4": s4} + ) + + assert fairpyx.divide(fairpyx.algorithms.SP_function, instance=instance) == {'s1': ['c1', 'c3', 'c2'], 's2': ['c3', 'c2', 'c4'], 's3': ['c4', 'c3', 'c5'], 's4': ['c1', 'c2', 'c5']}, "ERROR" + + +def test_different_k_for_students(): + s1 = {"c1": 400, "c2": 200, "c3": 150, "c4": 130, "c5": 120} + s2 = {"c1": 160, "c2": 350, "c3": 150, "c4": 140, "c5": 200} + s3 = {"c1": 300, "c2": 250, "c3": 110, "c4": 180, "c5": 160} + s4 = {"c1": 280, "c2": 250, "c3": 180, "c4": 130, "c5": 160} + s5 = {"c1": 140, "c2": 180, "c3": 270, "c4": 250, "c5": 160} + s6 = {"c1": 150, "c2": 250, "c3": 200, "c4": 260, "c5": 140} + s7 = {"c1": 250, "c2": 180, "c3": 210, "c4": 200, "c5": 160} + instance = fairpyx.Instance( + agent_capacities={"s1": 1, "s2": 1, "s3": 2, "s4": 3, "s5": 3, "s6": 4, "s7": 1}, + item_capacities={"c1": 2, "c2": 5, "c3": 4, "c4": 3, "c5": 2}, + valuations={"s1": s1, "s2": s2, "s3": s3, "s4": s4, "s5": s5, "s6": s6, "s7": s7} + ) + + assert fairpyx.divide(fairpyx.algorithms.SP_function, instance=instance) == {'s1': ['c1'], 's2': ['c2'], 's3': ['c1', 'c2'], 's4': ['c2', 'c3', 'c5'], 's5': ['c2', 'c3', 'c4'], 's6': ['c2', 'c3', 'c4', 'c5'], 's7': ['c3']}, "ERROR" + +def test_random(): + for i in range(NUM_OF_RANDOM_INSTANCES): + np.random.seed(i) + instance = fairpyx.Instance.random_uniform( + num_of_agents=70, num_of_items=10, normalized_sum_of_values=1000, + agent_capacity_bounds=[2,6], + item_capacity_bounds=[20,40], + item_base_value_bounds=[1,1000], + item_subjective_ratio_bounds=[0.5, 1.5] + ) + allocation = fairpyx.divide(fairpyx.algorithms.SP_function, instance=instance) + fairpyx.validate_allocation(instance, allocation, title=f"Seed {i}, SP_function") + +if __name__ == "__main__": + pytest.main(["-v",__file__]) diff --git a/tests/Tests_for_Optimization-based_Mechanisms/test_SP_O.py b/tests/Tests_for_Optimization-based_Mechanisms/test_SP_O.py new file mode 100644 index 0000000..be5dd57 --- /dev/null +++ b/tests/Tests_for_Optimization-based_Mechanisms/test_SP_O.py @@ -0,0 +1,67 @@ +""" +Test that course-allocation algorithms return a feasible solution. + +Programmer: OFEK, TAMAR, MORIYA ESTER +Since: 2024-03 +""" + +import pytest + +import fairpyx +import numpy as np + +NUM_OF_RANDOM_INSTANCES=10 + + +def test_optimal_change_result(): + s1 = {"c1": 50, "c2": 49, "c3": 1} + s2 = {"c1": 48, "c2": 46, "c3": 6} + instance = fairpyx.Instance( + agent_capacities={"s1": 1, "s2": 1, "s3": 1}, + item_capacities={"c1": 1, "c2": 1, "c3": 1}, + valuations={"s1": s1, "s2": s2} + ) + + assert fairpyx.divide(fairpyx.algorithms.SP_O_function, instance=instance) == {'s1': ['c2'], 's2': ['c1']}, "ERROR" + + +def test_optimal_improve_cardinal_and_ordinal_results(): + s1 = {"c1": 50, "c2": 30, "c3": 20} + s2 = {"c1": 40, "c2": 50, "c3": 10} + s3 = {"c1": 60, "c2": 10, "c3": 30} + instance = fairpyx.Instance( + agent_capacities={"s1": 2, "s2": 2, "s3": 2}, + item_capacities={"c1": 2, "c2": 3, "c3": 1}, + valuations={"s1": s1, "s2": s2, "s3": s3} + ) + + assert fairpyx.divide(fairpyx.algorithms.SP_O_function, instance=instance) == {'s1': ['c1', 'c2'], 's2': ['c2', 'c3'], 's3': ['c1', 'c2']}, "ERROR" + + +def test_sub_round_within_sub_round(): + s1 = {"c1": 40, "c2": 10, "c3": 20, "c4": 30} + s2 = {"c1": 50, "c2": 10, "c3": 15, "c4": 25} + s3 = {"c1": 60, "c2": 30, "c3": 2, "c4": 8} + instance = fairpyx.Instance( + agent_capacities={"s1": 2, "s2": 2, "s3": 2}, + item_capacities={"c1": 1, "c2": 2, "c3": 2, "c4": 1}, + valuations={"s1": s1, "s2": s2, "s3": s3} + ) + + assert fairpyx.divide(fairpyx.algorithms.SP_O_function, instance=instance) == {'s1': ['c3', 'c4'], 's2': ['c1', 'c2'], 's3': ['c2', 'c3']}, "ERROR" + + +def test_random(): + for i in range(NUM_OF_RANDOM_INSTANCES): + np.random.seed(i) + instance = fairpyx.Instance.random_uniform( + num_of_agents=70, num_of_items=10, normalized_sum_of_values=1000, + agent_capacity_bounds=[2,6], + item_capacity_bounds=[20,40], + item_base_value_bounds=[1,1000], + item_subjective_ratio_bounds=[0.5, 1.5] + ) + allocation = fairpyx.divide(fairpyx.algorithms.SP_O_function, instance=instance) + fairpyx.validate_allocation(instance, allocation, title=f"Seed {i}, SP_O_function") +if __name__ == "__main__": + pytest.main(["-v",__file__]) diff --git a/tests/Tests_for_Optimization-based_Mechanisms/test_TTC.py b/tests/Tests_for_Optimization-based_Mechanisms/test_TTC.py new file mode 100644 index 0000000..70b8492 --- /dev/null +++ b/tests/Tests_for_Optimization-based_Mechanisms/test_TTC.py @@ -0,0 +1,209 @@ +""" +Test that course-allocation algorithms return a feasible solution. + +Programmer: OFEK, TAMAR, MORIYA ESTER +Since: 2024-03 +""" + +import pytest + +import fairpyx +import numpy as np + +NUM_OF_RANDOM_INSTANCES = 10 +def test_small_example(): + s1 = {"c1": 50, "c2": 40, "c3": 5, "c4": 5} + s2 = {"c1": 20, "c2": 20, "c3": 30, "c4": 30} + s3 = {"c1": 60, "c2": 30, "c3": 1, "c4": 9} + + instance = fairpyx.Instance( + agent_capacities={"s1": 2, "s2": 2, "s3": 2}, + item_capacities={"c1": 1, "c2": 1, "c3": 2, "c4": 2}, + valuations={"s1": s1, "s2": s2, "s3": s3} + ) + + assert fairpyx.divide(fairpyx.algorithms.TTC_function, instance=instance) == {'s1': ['c2', 'c3'], 's2': ['c3', 'c4'], 's3': ['c1', 'c4']}, "ERROR" + +def test_big_example(): + s1 = {"c1": 50, "c2": 20, "c3": 10, "c4": 10, "c5": 10} + s2 = {"c1": 2, "c2": 3, "c3": 5, "c4": 30, "c5": 60} + s3 = {"c1": 20, "c2": 30, "c3": 10, "c4": 20, "c5": 20} + s4 = {"c1": 25, "c2": 0, "c3": 25, "c4": 25, "c5": 25} + s5 = {"c1": 5, "c2": 2, "c3": 1, "c4": 2, "c5": 25} + s6 = {"c1": 12, "c2": 14, "c3": 50, "c4": 12, "c5": 12} + s7 = {"c1": 1, "c2": 2, "c3": 2, "c4": 90, "c5": 5} + + instance = fairpyx.Instance( + agent_capacities={"s1": 4, "s2": 4, "s3": 4, "s4": 4, "s5": 4, "s6": 4, "s7": 4}, + item_capacities={"c1": 6, "c2": 6, "c3": 7, "c4": 3, "c5": 6}, + valuations={"s1": s1, "s2": s2, "s3": s3, "s4": s4, "s5": s5, "s6": s6, "s7": s7} + ) + + assert fairpyx.divide(fairpyx.algorithms.TTC_function, instance=instance) == {'s1': ['c1', 'c2', 'c3'], + 's2': ['c2', 'c3', 'c4', 'c5'], + 's3': ['c1', 'c2', 'c4', 'c5'], + 's4': ['c1', 'c3', 'c5'], + 's5': ['c1', 'c2', 'c3', 'c5'], + 's6': ['c1', 'c2', 'c3', 'c5'], + 's7': ['c2', 'c3', 'c4', 'c5']}, "ERROR" + + +def test_same_order_of_course_selection(): + s1 = {"c1": 10, "c2": 20, "c3": 10, "c4": 50, "c5": 10} + s2 = {"c1": 5, "c2": 30, "c3": 3, "c4": 60, "c5": 2} + s3 = {"c1": 20, "c2": 20, "c3": 20, "c4": 30, "c5": 10} + s4 = {"c1": 25, "c2": 25, "c3": 25, "c4": 25, "c5": 0} + s5 = {"c1": 2, "c2": 5, "c3": 2, "c4": 90, "c5": 1} + s6 = {"c1": 12, "c2": 14, "c3": 12, "c4": 50, "c5": 12} + s7 = {"c1": 2, "c2": 5, "c3": 2, "c4": 90, "c5": 1} + + instance = fairpyx.Instance( + agent_capacities={"s1": 4, "s2": 4, "s3": 4, "s4": 4, "s5": 4, "s6": 4, "s7": 4}, + item_capacities={"c1": 6, "c2": 6, "c3": 7, "c4": 3, "c5": 6}, + valuations={"s1": s1, "s2": s2, "s3": s3, "s4": s4, "s5": s5, "s6": s6, "s7": s7} + ) + + assert fairpyx.divide(fairpyx.algorithms.TTC_function, instance=instance) == {'s1': ['c1', 'c2', 'c3', 'c5'], + 's2': ['c1', 'c2', 'c3', 'c4'], + 's3': ['c1', 'c2', 'c3', 'c5'], + 's4': ['c1', 'c2', 'c3'], + 's5': ['c2', 'c3', 'c4', 'c5'], + 's6': ['c1', 'c2', 'c3', 'c5'], + 's7': ['c1', 'c3', 'c4', 'c5']}, "ERROR" + + +def test_suboptimal_cardinal_utility(): + s1 = {"c1": 30, "c2": 35, "c3": 35} + s2 = {"c1": 10, "c2": 80, "c3": 10} + s3 = {"c1": 34, "c2": 32, "c3": 34} + + instance = fairpyx.Instance( + agent_capacities={"s1": 1, "s2": 1, "s3": 1}, + item_capacities={"c1": 2, "c2": 1, "c3": 1}, + valuations={"s1": s1, "s2": s2, "s3": s3} + ) + + assert fairpyx.divide(fairpyx.algorithms.TTC_function, instance=instance) == {'s1': ['c1'], + 's2': ['c2'], + 's3': ['c3']}, "ERROR" + +def test_optimal_change_result(): + s1 = {"c1": 50, "c2": 49, "c3": 1} + s2 = {"c1": 48, "c2": 46, "c3": 6} + instance = fairpyx.Instance( + agent_capacities={"s1": 1, "s2": 1, "s3": 1}, + item_capacities={"c1": 1, "c2": 1, "c3": 1}, + valuations={"s1": s1, "s2": s2} + ) + + assert fairpyx.divide(fairpyx.algorithms.TTC_function, instance=instance) == {'s1': ['c1'], 's2': ['c2']}, "ERROR" + + +def test_sub_round_within_round(): + s1 = {"c1": 44, "c2": 39, "c3": 17} + s2 = {"c1": 50, "c2": 45, "c3": 5} + s3 = {"c1": 45, "c2": 40, "c3": 15} + instance = fairpyx.Instance( + agent_capacities={"s1": 1, "s2": 1, "s3": 1}, + item_capacities={"c1": 1, "c2": 1, "c3": 1}, + valuations={"s1": s1, "s2": s2, "s3": s3} + ) + + assert fairpyx.divide(fairpyx.algorithms.TTC_function, instance=instance) == {'s1': ['c3'], 's2': ['c1'], 's3': ['c2']}, "ERROR" + + +def test_students_with_the_same_list(): + s1 = {"c1": 55, "c2": 45} + s2 = {"c1": 55, "c2": 45} + instance = fairpyx.Instance( + agent_capacities={"s1": 1, "s2": 1}, + item_capacities={"c1": 1, "c2": 1}, + valuations={"s1": s1, "s2": s2} + ) + + assert fairpyx.divide(fairpyx.algorithms.TTC_function, instance=instance) == {'s1': ['c1'], 's2': ['c2']}, "ERROR" + + +def test_student_dont_get_k_courses(): + s1 = {"c1": 50, "c2": 10, "c3": 40} + s2 = {"c1": 45, "c2": 30, "c3": 25} + s3 = {"c1": 49, "c2": 15, "c3": 36} + instance = fairpyx.Instance( + agent_capacities={"s1": 2, "s2": 2, "s3": 2}, + item_capacities={"c1": 2, "c2": 2, "c3": 2}, + valuations={"s1": s1, "s2": s2, "s3": s3} + ) + + assert fairpyx.divide(fairpyx.algorithms.TTC_function, instance=instance) == {'s1': ['c1', 'c3'], 's2': ['c2'], 's3': ['c1', 'c3']}, "ERROR" + + +def test_sub_round_within_sub_round(): + s1 = {"c1": 40, "c2": 10, "c3": 20, "c4": 30} + s2 = {"c1": 50, "c2": 10, "c3": 15, "c4": 25} + s3 = {"c1": 60, "c2": 30, "c3": 2, "c4": 8} + instance = fairpyx.Instance( + agent_capacities={"s1": 2, "s2": 2, "s3": 2}, + item_capacities={"c1": 1, "c2": 2, "c3": 2, "c4": 1}, + valuations={"s1": s1, "s2": s2, "s3": s3} + ) + + assert fairpyx.divide(fairpyx.algorithms.TTC_function, instance=instance) == {'s1': ['c3', 'c4'], 's2': ['c2', 'c3'], 's3': ['c1', 'c2']}, "ERROR" + +def test_student_bids_the_same_for_different_courses(): + s1 = {"c1": 50, "c2": 50} + s2 = {"c1": 40, "c2": 60} + s3 = {"c1": 75, "c2": 25} + instance = fairpyx.Instance( + agent_capacities={"s1": 1, "s2": 1, "s3": 1}, + item_capacities={"c1": 2, "c2": 1}, + valuations={"s1": s1, "s2": s2, "s3": s3} + ) + + assert fairpyx.divide(fairpyx.algorithms.TTC_function, instance=instance) == {'s1': ['c1'], 's2': ['c2'], 's3': ['c1']}, "ERROR" + +def test_from_the_article(): + s1 = {"c1": 400, "c2": 150, "c3": 230, "c4": 200, "c5": 20} + s2 = {"c1": 245, "c2": 252, "c3": 256, "c4": 246, "c5": 1} + s3 = {"c1": 243, "c2": 230, "c3": 240, "c4": 245, "c5": 42} + s4 = {"c1": 251, "c2": 235, "c3": 242, "c4": 201, "c5": 71} + instance = fairpyx.Instance( + agent_capacities={"s1": 3, "s2": 3, "s3": 3, "s4": 3}, + item_capacities={"c1": 2, "c2": 3, "c3": 3, "c4": 2, "c5": 2}, + item_conflicts={"c1": ['c4'], "c4": ['c1']}, + valuations={"s1": s1, "s2": s2, "s3": s3, "s4": s4} + ) + + assert fairpyx.divide(fairpyx.algorithms.TTC_function, instance=instance) == {'s1': ['c1', 'c2', 'c5'], 's2': ['c2', 'c3', 'c4'], 's3': ['c3', 'c4', 'c5'], 's4': ['c1', 'c2', 'c3']}, "ERROR" + +def test_different_k_for_students(): + s1 = {"c1": 400, "c2": 200, "c3": 150, "c4": 130, "c5": 120} + s2 = {"c1": 160, "c2": 350, "c3": 150, "c4": 140, "c5": 200} + s3 = {"c1": 300, "c2": 250, "c3": 110, "c4": 180, "c5": 160} + s4 = {"c1": 280, "c2": 250, "c3": 180, "c4": 130, "c5": 160} + s5 = {"c1": 140, "c2": 180, "c3": 270, "c4": 250, "c5": 160} + s6 = {"c1": 150, "c2": 250, "c3": 200, "c4": 260, "c5": 140} + s7 = {"c1": 250, "c2": 180, "c3": 210, "c4": 200, "c5": 160} + instance = fairpyx.Instance( + agent_capacities={"s1": 1, "s2": 1, "s3": 2, "s4": 3, "s5": 3, "s6": 4, "s7": 1}, + item_capacities={"c1": 2, "c2": 5, "c3": 4, "c4": 3, "c5": 2}, + valuations={"s1": s1, "s2": s2, "s3": s3, "s4": s4, "s5": s5, "s6": s6, "s7": s7} + ) + + assert fairpyx.divide(fairpyx.algorithms.TTC_function, instance=instance) == {'s1': ['c1'], 's2': ['c2'], 's3': ['c1', 'c2'], 's4': ['c2', 'c3', 'c5'], 's5': ['c2', 'c3', 'c4'], 's6': ['c2', 'c3', 'c4', 'c5'], 's7': ['c3']}, "ERROR" + + +def test_random(): + for i in range(NUM_OF_RANDOM_INSTANCES): + np.random.seed(i) + instance = fairpyx.Instance.random_uniform( + num_of_agents=70, num_of_items=10, normalized_sum_of_values=1000, + agent_capacity_bounds=[2,6], + item_capacity_bounds=[20,40], + item_base_value_bounds=[1,1000], + item_subjective_ratio_bounds=[0.5, 1.5] + ) + allocation = fairpyx.divide(fairpyx.algorithms.TTC_function, instance=instance) + fairpyx.validate_allocation(instance, allocation, title=f"Seed {i}, TTC_function") + +if __name__ == "__main__": + pytest.main(["-v",__file__]) diff --git a/tests/Tests_for_Optimization-based_Mechanisms/test_TTC_O.py b/tests/Tests_for_Optimization-based_Mechanisms/test_TTC_O.py new file mode 100644 index 0000000..b363e0f --- /dev/null +++ b/tests/Tests_for_Optimization-based_Mechanisms/test_TTC_O.py @@ -0,0 +1,95 @@ +""" +Test that course-allocation algorithms return a feasible solution. + +Programmer: OFEK, TAMAR, MORIYA ESTER +Since: 2024-03 +""" + +import pytest + +import fairpyx +import numpy as np + + +NUM_OF_RANDOM_INSTANCES=10 + +def test_optimal_change_result(): + s1 = {"c1": 50, "c2": 49, "c3": 1} + s2 = {"c1": 48, "c2": 46, "c3": 6} + instance = fairpyx.Instance( + agent_capacities={"s1": 1, "s2": 1, "s3": 1}, + item_capacities={"c1": 1, "c2": 1, "c3": 1}, + valuations={"s1": s1, "s2": s2} + ) + + assert fairpyx.divide(fairpyx.algorithms.TTC_O_function, instance=instance) == {'s1': ['c2'], 's2': ['c1']}, "ERROR" + + +def test_student_get_k_courses(): # in ttc a student didn't get k cources + s1 = {"c1": 50, "c2": 10, "c3": 40} + s2 = {"c1": 45, "c2": 30, "c3": 25} + s3 = {"c1": 49, "c2": 15, "c3": 36} + instance = fairpyx.Instance( + agent_capacities={"s1": 2, "s2": 2, "s3": 2}, + item_capacities={"c1": 2, "c2": 2, "c3": 2}, + valuations={"s1": s1, "s2": s2, "s3": s3} + ) + + assert fairpyx.divide(fairpyx.algorithms.TTC_O_function, instance=instance) == {'s1': ['c2', 'c3'], 's2': ['c1', 'c2'], 's3': ['c1', 'c3']}, "ERROR" + + +def test_optimal_improve_cardinal_and_ordinal_results(): + s1 = {"c1": 50, "c2": 30, "c3": 20} + s2 = {"c1": 40, "c2": 50, "c3": 10} + s3 = {"c1": 60, "c2": 10, "c3": 30} + instance = fairpyx.Instance( + agent_capacities={"s1": 2, "s2": 2, "s3": 2}, + item_capacities={"c1": 2, "c2": 3, "c3": 1}, + valuations={"s1": s1, "s2": s2, "s3": s3} + ) + + assert fairpyx.divide(fairpyx.algorithms.TTC_O_function, instance=instance) == {'s1': ['c1', 'c2'], 's2': ['c2', 'c3'], 's3': ['c1', 'c2']}, "ERROR" + + +def test_sub_round_within_sub_round(): + s1 = {"c1": 40, "c2": 10, "c3": 20, "c4": 30} + s2 = {"c1": 50, "c2": 10, "c3": 15, "c4": 25} + s3 = {"c1": 60, "c2": 30, "c3": 2, "c4": 8} + instance = fairpyx.Instance( + agent_capacities={"s1": 2, "s2": 2, "s3": 2}, + item_capacities={"c1": 1, "c2": 2, "c3": 2, "c4": 1}, + valuations={"s1": s1, "s2": s2, "s3": s3} + ) + + assert fairpyx.divide(fairpyx.algorithms.TTC_O_function, instance=instance) == {'s1': ['c3', 'c4'], 's2': ['c1', 'c2'], 's3': ['c2', 'c3']}, "ERROR" + +def test_optimal_cardinal_utility(): + s1 = {"c1": 30, "c2": 35, "c3": 35} + s2 = {"c1": 10, "c2": 80, "c3": 10} + s3 = {"c1": 34, "c2": 32, "c3": 34} + + instance = fairpyx.Instance( + agent_capacities={"s1": 1, "s2": 1, "s3": 1}, + item_capacities={"c1": 2, "c2": 1, "c3": 1}, + valuations={"s1": s1, "s2": s2, "s3":s3} + ) + + assert fairpyx.divide(fairpyx.algorithms.TTC_O_function, instance=instance) == {'s1': ['c3'], + 's2': ['c2'], + 's3': ['c1']}, "ERROR" + +def test_random(): + for i in range(NUM_OF_RANDOM_INSTANCES): + np.random.seed(i) + instance = fairpyx.Instance.random_uniform( + num_of_agents=70, num_of_items=10, normalized_sum_of_values=1000, + agent_capacity_bounds=[2, 6], + item_capacity_bounds=[20, 40], + item_base_value_bounds=[1, 1000], + item_subjective_ratio_bounds=[0.5, 1.5] + ) + allocation = fairpyx.divide(fairpyx.algorithms.TTC_O_function, instance=instance) + fairpyx.validate_allocation(instance, allocation, title=f"Seed {i}, TTC_O_function") + +if __name__ == "__main__": + pytest.main(["-v",__file__]) From 5200a74db1c254ec547965c87926ee7870ef01fb Mon Sep 17 00:00:00 2001 From: ofekats Date: Wed, 8 May 2024 18:34:25 +0300 Subject: [PATCH 02/42] init in algo --- fairpyx/algorithms/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fairpyx/algorithms/__init__.py b/fairpyx/algorithms/__init__.py index 125042d..cf404b0 100644 --- a/fairpyx/algorithms/__init__.py +++ b/fairpyx/algorithms/__init__.py @@ -2,3 +2,8 @@ from fairpyx.algorithms.iterated_maximum_matching import iterated_maximum_matching, iterated_maximum_matching_adjusted, iterated_maximum_matching_unadjusted from fairpyx.algorithms.picking_sequence import round_robin, bidirectional_round_robin, serial_dictatorship from fairpyx.algorithms.utilitarian_matching import utilitarian_matching +from fairpyx.algorithms.Optimization_based_Mechanisms.OC import OC_function +from fairpyx.algorithms.Optimization_based_Mechanisms.SP_O import SP_O_function +from fairpyx.algorithms.Optimization_based_Mechanisms.SP import SP_function +from fairpyx.algorithms.Optimization_based_Mechanisms.TTC_O import TTC_O_function +from fairpyx.algorithms.Optimization_based_Mechanisms.TTC import TTC_function \ No newline at end of file From d004277063259b61ac26ae9a8a8d493d3b58974e Mon Sep 17 00:00:00 2001 From: ofekats Date: Wed, 8 May 2024 18:40:51 +0300 Subject: [PATCH 03/42] =?UTF-8?q?changed=20=C2=96remove=5Fagent=20to=20rem?= =?UTF-8?q?ove=5Fagent=5Ffrom=5Floop?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py index e7adbdb..8fbac0f 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py @@ -62,7 +62,7 @@ def TTC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger for current_agent in agents_with_no_potential_items: logger.info("Agent %s cannot pick any more items: remaining=%s, bundle=%s", current_agent, alloc.remaining_item_capacities, alloc.bundles[current_agent]) - alloc.remove_agent(current_agent) + alloc.remove_agent_from_loop(current_agent) agents_who_need_an_item_in_current_iteration.remove(current_agent) # 2. Allocate the remaining seats in each course: From e916e255cb8d9eebaaa4d569ad59eb10bc3eff18 Mon Sep 17 00:00:00 2001 From: ofekats Date: Wed, 8 May 2024 19:05:02 +0300 Subject: [PATCH 04/42] =?UTF-8?q?changed=20=C2=96remove=5Fagent=20to=20rem?= =?UTF-8?q?ove=5Fagent=5Ffrom=5Floop?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fairpyx/algorithms/Optimization_based_Mechanisms/SP.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py b/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py index 2d81333..c123a20 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py @@ -58,7 +58,7 @@ def SP_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger logger.info("Agent %s cannot pick any more items: remaining=%s, bundle=%s", current_agent, alloc.remaining_item_capacities, alloc.bundles[current_agent]) if current_agent in agent_order and current_agent in alloc.remaining_agent_capacities: # checking if the agent in dict before removing - alloc.remove_agent(current_agent) + alloc.remove_agent_from_loop(current_agent) agent_order.remove(current_agent) continue From 28edbe9f29da848fbe2b897e1c265a6c2b47f2f3 Mon Sep 17 00:00:00 2001 From: ofekats Date: Wed, 8 May 2024 19:37:58 +0300 Subject: [PATCH 05/42] cttc all tests passed sp not good yet --- .../test_SP.py | 35 ++++---- .../test_TTC.py | 79 ++++++------------- 2 files changed, 42 insertions(+), 72 deletions(-) diff --git a/tests/Tests_for_Optimization-based_Mechanisms/test_SP.py b/tests/Tests_for_Optimization-based_Mechanisms/test_SP.py index 5961ba7..0753e3a 100644 --- a/tests/Tests_for_Optimization-based_Mechanisms/test_SP.py +++ b/tests/Tests_for_Optimization-based_Mechanisms/test_SP.py @@ -11,12 +11,12 @@ import fairpyx import numpy as np -NUM_OF_RANDOM_INSTANCES=10 +NUM_OF_RANDOM_INSTANCES = 10 def test_number_of_courses_is_not_optimal(): - s1 = {"c1": 25, "c2": 25, "c3": 25, "c4": 25} - s2 = {"c1": 20, "c2": 20, "c3": 40, "c4": 20} + s1 = {"c1": 28, "c2": 26, "c3": 19, "c4": 27} + s2 = {"c1": 21, "c2": 20, "c3": 40, "c4": 19} instance = fairpyx.Instance( agent_capacities={"s1": 3, "s2": 3}, item_capacities={"c1": 1, "c2": 2, "c3": 2, "c4": 1}, @@ -33,21 +33,19 @@ def test_small_example(): instance = fairpyx.Instance( agent_capacities={"s1": 2, "s2": 2, "s3": 2}, item_capacities={"c1": 1, "c2": 1, "c3": 2, "c4": 2}, - valuations={"s1": s1, "s2": s2, "s3":s3} + valuations={"s1": s1, "s2": s2, "s3": s3} ) - assert fairpyx.divide(fairpyx.algorithms.SP_function, instance=instance) == {'s1': ['c2', 'c3'], - 's2': ['c3', 'c4'], - 's3': ['c1', 'c4']}, "ERROR" + assert fairpyx.divide(fairpyx.algorithms.SP_function, instance=instance) == {'s1': ['c2', 'c3'], 's2': ['c3', 'c4'], 's3': ['c1', 'c4']}, "ERROR" def test_big_example(): - s1 = {"c1": 50, "c2": 20, "c3": 10, "c4": 10,"c5": 10} - s2 = {"c1": 2, "c2": 3, "c3": 5, "c4": 30,"c5": 60} - s3 = {"c1": 20, "c2": 30, "c3": 10, "c4": 20,"c5": 20} - s4 = {"c1": 25, "c2": 0, "c3": 25, "c4": 25,"c5": 25} - s5 = {"c1": 5, "c2": 2, "c3": 1, "c4": 2,"c5": 25} - s6 = {"c1": 12, "c2": 14, "c3": 50, "c4": 12,"c5": 12} - s7 = {"c1": 1, "c2": 2, "c3": 2, "c4": 5,"c5": 90} + s1 = {"c1": 50, "c2": 20, "c3": 11, "c4": 10, "c5": 9} + s2 = {"c1": 4, "c2": 5, "c3": 7, "c4": 26, "c5": 60} + s3 = {"c1": 20, "c2": 30, "c3": 10, "c4": 21, "c5": 19} + s4 = {"c1": 24, "c2": 8, "c3": 25, "c4": 17, "c5": 26} + s5 = {"c1": 5, "c2": 2, "c3": 1, "c4": 3, "c5": 90} + s6 = {"c1": 13, "c2": 15, "c3": 49, "c4": 11, "c5": 12} + s7 = {"c1": 3, "c2": 4, "c3": 6, "c4": 70, "c5": 19} instance = fairpyx.Instance( agent_capacities={"s1": 4, "s2": 4, "s3": 4, "s4": 4, "s5": 4, "s6": 4, "s7": 4}, @@ -55,14 +53,13 @@ def test_big_example(): valuations={"s1": s1, "s2": s2, "s3": s3, "s4": s4, "s5": s5, "s6": s6, "s7": s7} ) - assert fairpyx.divide(fairpyx.algorithms.SP_function, instance=instance) == {'s1': ['c1', 'c2', 'c3', 'c5'], - 's2': ['c1', 'c3', 'c4', 'c5'], + assert fairpyx.divide(fairpyx.algorithms.SP_function, instance=instance) == {'s1': ['c1', 'c2', 'c3'], + 's2': ['c2', 'c3', 'c4', 'c5'], 's3': ['c1', 'c2', 'c4', 'c5'], 's4': ['c1', 'c2', 'c3', 'c5'], 's5': ['c1', 'c2', 'c3', 'c5'], - 's6': ['c1', 'c2', 'c3'], - 's7': ['c2', 'c3', 'c4', 'c5']}, "ERROR" - + 's6': ['c1', 'c2', 'c3', 'c5'], + 's7': ['c1', 'c3', 'c4', 'c5']}, "ERROR" def test_same_order_of_course_selection(): s1 = {"c1": 10, "c2": 20, "c3": 10, "c4": 50, "c5": 10} diff --git a/tests/Tests_for_Optimization-based_Mechanisms/test_TTC.py b/tests/Tests_for_Optimization-based_Mechanisms/test_TTC.py index 70b8492..05a47e9 100644 --- a/tests/Tests_for_Optimization-based_Mechanisms/test_TTC.py +++ b/tests/Tests_for_Optimization-based_Mechanisms/test_TTC.py @@ -25,13 +25,13 @@ def test_small_example(): assert fairpyx.divide(fairpyx.algorithms.TTC_function, instance=instance) == {'s1': ['c2', 'c3'], 's2': ['c3', 'c4'], 's3': ['c1', 'c4']}, "ERROR" def test_big_example(): - s1 = {"c1": 50, "c2": 20, "c3": 10, "c4": 10, "c5": 10} - s2 = {"c1": 2, "c2": 3, "c3": 5, "c4": 30, "c5": 60} - s3 = {"c1": 20, "c2": 30, "c3": 10, "c4": 20, "c5": 20} - s4 = {"c1": 25, "c2": 0, "c3": 25, "c4": 25, "c5": 25} - s5 = {"c1": 5, "c2": 2, "c3": 1, "c4": 2, "c5": 25} - s6 = {"c1": 12, "c2": 14, "c3": 50, "c4": 12, "c5": 12} - s7 = {"c1": 1, "c2": 2, "c3": 2, "c4": 90, "c5": 5} + s1 = {"c1": 50, "c2": 20, "c3": 11, "c4": 10, "c5": 9} + s2 = {"c1": 4, "c2": 5, "c3": 7, "c4": 26, "c5": 60} + s3 = {"c1": 20, "c2": 30, "c3": 10, "c4": 21, "c5": 19} + s4 = {"c1": 24, "c2": 8, "c3": 25, "c4": 17, "c5": 26} + s5 = {"c1": 5, "c2": 2, "c3": 1, "c4": 3, "c5": 90} + s6 = {"c1": 13, "c2": 15, "c3": 49, "c4": 11, "c5": 12} + s7 = {"c1": 3, "c2": 4, "c3": 6, "c4": 70, "c5": 19} instance = fairpyx.Instance( agent_capacities={"s1": 4, "s2": 4, "s3": 4, "s4": 4, "s5": 4, "s6": 4, "s7": 4}, @@ -42,40 +42,15 @@ def test_big_example(): assert fairpyx.divide(fairpyx.algorithms.TTC_function, instance=instance) == {'s1': ['c1', 'c2', 'c3'], 's2': ['c2', 'c3', 'c4', 'c5'], 's3': ['c1', 'c2', 'c4', 'c5'], - 's4': ['c1', 'c3', 'c5'], + 's4': ['c1', 'c2', 'c3', 'c5'], 's5': ['c1', 'c2', 'c3', 'c5'], 's6': ['c1', 'c2', 'c3', 'c5'], - 's7': ['c2', 'c3', 'c4', 'c5']}, "ERROR" - - -def test_same_order_of_course_selection(): - s1 = {"c1": 10, "c2": 20, "c3": 10, "c4": 50, "c5": 10} - s2 = {"c1": 5, "c2": 30, "c3": 3, "c4": 60, "c5": 2} - s3 = {"c1": 20, "c2": 20, "c3": 20, "c4": 30, "c5": 10} - s4 = {"c1": 25, "c2": 25, "c3": 25, "c4": 25, "c5": 0} - s5 = {"c1": 2, "c2": 5, "c3": 2, "c4": 90, "c5": 1} - s6 = {"c1": 12, "c2": 14, "c3": 12, "c4": 50, "c5": 12} - s7 = {"c1": 2, "c2": 5, "c3": 2, "c4": 90, "c5": 1} - - instance = fairpyx.Instance( - agent_capacities={"s1": 4, "s2": 4, "s3": 4, "s4": 4, "s5": 4, "s6": 4, "s7": 4}, - item_capacities={"c1": 6, "c2": 6, "c3": 7, "c4": 3, "c5": 6}, - valuations={"s1": s1, "s2": s2, "s3": s3, "s4": s4, "s5": s5, "s6": s6, "s7": s7} - ) - - assert fairpyx.divide(fairpyx.algorithms.TTC_function, instance=instance) == {'s1': ['c1', 'c2', 'c3', 'c5'], - 's2': ['c1', 'c2', 'c3', 'c4'], - 's3': ['c1', 'c2', 'c3', 'c5'], - 's4': ['c1', 'c2', 'c3'], - 's5': ['c2', 'c3', 'c4', 'c5'], - 's6': ['c1', 'c2', 'c3', 'c5'], 's7': ['c1', 'c3', 'c4', 'c5']}, "ERROR" - -def test_suboptimal_cardinal_utility(): - s1 = {"c1": 30, "c2": 35, "c3": 35} - s2 = {"c1": 10, "c2": 80, "c3": 10} - s3 = {"c1": 34, "c2": 32, "c3": 34} +def test_small_number_2(): + s1 = {"c1": 30, "c2": 35, "c3": 36} + s2 = {"c1": 10, "c2": 80, "c3": 11} + s3 = {"c1": 34, "c2": 32, "c3": 35} instance = fairpyx.Instance( agent_capacities={"s1": 1, "s2": 1, "s3": 1}, @@ -83,9 +58,20 @@ def test_suboptimal_cardinal_utility(): valuations={"s1": s1, "s2": s2, "s3": s3} ) - assert fairpyx.divide(fairpyx.algorithms.TTC_function, instance=instance) == {'s1': ['c1'], + assert fairpyx.divide(fairpyx.algorithms.TTC_function, instance=instance) == {'s1': ['c3'], 's2': ['c2'], - 's3': ['c3']}, "ERROR" + 's3': ['c1']}, "ERROR" + +def test_number_of_courses_is_not_optimal(): + s1 = {"c1": 28, "c2": 26, "c3": 19, "c4": 27} + s2 = {"c1": 21, "c2": 20, "c3": 40, "c4": 19} + instance = fairpyx.Instance( + agent_capacities={"s1": 3, "s2": 3}, + item_capacities={"c1": 1, "c2": 2, "c3": 2, "c4": 1}, + valuations={"s1": s1, "s2": s2} + ) + assert fairpyx.divide(fairpyx.algorithms.TTC_function, instance=instance) == {'s1': ['c1', 'c2', 'c4'], 's2': ['c2', 'c3']}, "ERROR" + def test_optimal_change_result(): s1 = {"c1": 50, "c2": 49, "c3": 1} @@ -111,19 +97,6 @@ def test_sub_round_within_round(): assert fairpyx.divide(fairpyx.algorithms.TTC_function, instance=instance) == {'s1': ['c3'], 's2': ['c1'], 's3': ['c2']}, "ERROR" - -def test_students_with_the_same_list(): - s1 = {"c1": 55, "c2": 45} - s2 = {"c1": 55, "c2": 45} - instance = fairpyx.Instance( - agent_capacities={"s1": 1, "s2": 1}, - item_capacities={"c1": 1, "c2": 1}, - valuations={"s1": s1, "s2": s2} - ) - - assert fairpyx.divide(fairpyx.algorithms.TTC_function, instance=instance) == {'s1': ['c1'], 's2': ['c2']}, "ERROR" - - def test_student_dont_get_k_courses(): s1 = {"c1": 50, "c2": 10, "c3": 40} s2 = {"c1": 45, "c2": 30, "c3": 25} @@ -206,4 +179,4 @@ def test_random(): fairpyx.validate_allocation(instance, allocation, title=f"Seed {i}, TTC_function") if __name__ == "__main__": - pytest.main(["-v",__file__]) + pytest.main(["-v",__file__]) \ No newline at end of file From 35eb2be88ad054afab86b1803583b0b77c75cdb5 Mon Sep 17 00:00:00 2001 From: ofekats Date: Thu, 9 May 2024 20:14:11 +0300 Subject: [PATCH 06/42] sp short and working --- .../Optimization_based_Mechanisms/SP.py | 139 ++++++++++-------- .../Optimization_based_Mechanisms/TTC.py | 19 +-- 2 files changed, 81 insertions(+), 77 deletions(-) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py b/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py index c123a20..3017cf0 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py @@ -31,83 +31,94 @@ def SP_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger explanation_logger.info("\nAlgorithm SP starts.\n") - agent_order = [] # list of all the instances of agents as many as their items capacities (from the article: sum of Ki) + # agent_order = [] # list of all the instances of agents as many as their items capacities (from the article: sum of Ki) map_agent_to_best_item = {} # dict of the max bids for each agent in specific iteration (from the article: max{i, b}) map_course_to_students_with_max_bids = {c: {} for c in alloc.remaining_item_capacities} - for agent, capacity in alloc.remaining_agent_capacities.items(): # save the agent number of his needed courses (שומר את הסטודנט מספר פעמים כמספר הקורסים שהוא צריך) - agent_order.extend([agent] * capacity) + #for agent, capacity in alloc.remaining_agent_capacities.items(): # save the agent number of his needed courses (שומר את הסטודנט מספר פעמים כמספר הקורסים שהוא צריך) + # agent_order.extend([agent] * capacity) - set_agent = list(dict.fromkeys(agent_order)) # set of the agents by thier order . each agent appears only once + #set_agent = list(dict.fromkeys(agent_order)) # set of the agents by thier order . each agent appears only once - dict_extra_bids = {s: 0 for s in set_agent} + map_student_to_his_sum_bids = {s: 0 for s in alloc.remaining_agents()} - for agent in agent_order: - if agent not in alloc.remaining_agent_capacities: # to check if not all the agents got k courses - continue - - while set_agent: # round i - for current_agent in set_agent: # check the course with the max bids for each agent in the set (who didnt get a course in this round) - if len(alloc.remaining_agent_capacities) == 0 or len( - alloc.remaining_item_capacities) == 0: # check if all the agents got their courses or there are no more - set_agent = [] - break - - potential_items_for_agent = set(alloc.remaining_items()).difference(alloc.bundles[current_agent]) # set of all the courses that have places sub the courses the agent got (under the assumption that if agent doesnt want a course the course got 0 bids automaticlly) + max_iterations = max(alloc.remaining_agent_capacities[agent] for agent in alloc.remaining_agents()) + for iteration in range(max_iterations): # External loop of algorithm: in each iteration, each student gets 1 seat (if possible). + if len(alloc.remaining_agent_capacities) == 0 or len(alloc.remaining_item_capacities) == 0: # check if all the agents got their courses or there are no more + break + agents_who_need_an_item_in_current_iteration = set(alloc.remaining_agents()) + while agents_who_need_an_item_in_current_iteration: # round i + # 1. Create the dict map_agent_to_best_item + agents_with_no_potential_items = set() + for current_agent in agents_who_need_an_item_in_current_iteration: # check the course with the max bids for each agent in the set (who didnt get a course in this round) + # potential_items_for_agent = set(alloc.remaining_items()).difference(alloc.bundles[current_agent]) # set of all the courses that have places sub the courses the agent got (under the assumption that if agent doesnt want a course the course got 0 bids automaticlly) + potential_items_for_agent = alloc.remaining_items_for_agent(current_agent) # set of all the courses that have places sub the courses the agent got (under the assumption that if agent doesnt want a course the course got 0 bids automaticlly) if len(potential_items_for_agent) == 0: - logger.info("Agent %s cannot pick any more items: remaining=%s, bundle=%s", current_agent, - alloc.remaining_item_capacities, alloc.bundles[current_agent]) - if current_agent in agent_order and current_agent in alloc.remaining_agent_capacities: # checking if the agent in dict before removing - alloc.remove_agent_from_loop(current_agent) - agent_order.remove(current_agent) - continue - - map_agent_to_best_item[current_agent] = max(potential_items_for_agent, - key=lambda item: alloc.effective_value(current_agent, - item)) # for each agent save the course with the max bids from the potential courses only - # update bids for all student in dict_extra_bids - for s in dict_extra_bids: - if s in map_agent_to_best_item: - dict_extra_bids[s] += alloc.effective_value(s ,map_agent_to_best_item[s]) + agents_with_no_potential_items.add(current_agent) + else: + map_agent_to_best_item[current_agent] = max(potential_items_for_agent, + key=lambda item: alloc.effective_value(current_agent,item)) # for each agent save the course with the max bids from the potential courses only + + for current_agent in agents_with_no_potential_items: + logger.info("Agent %s cannot pick any more items: remaining=%s, bundle=%s", current_agent, + alloc.remaining_item_capacities, alloc.bundles[current_agent]) + alloc.remove_agent_from_loop(current_agent) + agents_who_need_an_item_in_current_iteration.remove(current_agent) + + #map_student_to_his_sum_bids += [alloc.effective_value(student,course) for student, course in map_student_to_his_sum_bids.items()] + for student, course in map_student_to_his_sum_bids.items(): + if student in map_agent_to_best_item: + map_student_to_his_sum_bids[student] += alloc.effective_value(student ,course) #create dict for each course of student that point on the course and their bids map_course_to_students_with_max_bids = {course: - {student: dict_extra_bids[student] for student in map_agent_to_best_item if map_agent_to_best_item[student] == course} - for course in alloc.remaining_items() - } - # for c in map_course_to_students_with_bids: - # map_course_to_students_with_bids[c] = {s: dict_extra_bids[s] for s in map_agent_to_best_item if map_agent_to_best_item[s] == c} - # for s in map_agent_to_best_item: - # if map_agent_to_best_item[s] == c: - # dict_s_to_c[s] = dict_extra_bids[s] - # dict_by_course[c] = dict_s_to_c - - #loop on the dict_by_course and for each curse give it to as many studens it can + {student: map_student_to_his_sum_bids[student] for student in map_agent_to_best_item if map_agent_to_best_item[student] == course} + for course in alloc.remaining_items()} + + #loop on the dict_by_course and for each course give it to as many studens it can for course in map_course_to_students_with_max_bids: if course in alloc.remaining_item_capacities: - c_capacity = alloc.remaining_item_capacities[course] - if len(map_course_to_students_with_max_bids[course]) == 0: - continue - elif len(map_course_to_students_with_max_bids[course]) <= c_capacity: - for s in map_course_to_students_with_max_bids[course]: - alloc.give(s,course,logger) - set_agent.remove(s) # removing the agent from the set (dont worry he will come back in the next round) - if s in map_agent_to_best_item: # removing the agent from the max dict - del map_agent_to_best_item[s] + + + sorted_students_pointing_to_course = sorted( + [student for student in map_agent_to_best_item if map_agent_to_best_item[student] == course], + key=lambda student: alloc.effective_value(student, course), + reverse=True) # sort the keys by their values (descending order) + remaining_capacity = alloc.remaining_item_capacities[course] + sorted_students_who_can_get_course = sorted_students_pointing_to_course[:remaining_capacity] + price = 0 + if len(sorted_students_pointing_to_course) > remaining_capacity: + price = map_student_to_his_sum_bids[sorted_students_pointing_to_course[remaining_capacity]] # the amount of bids of the first studen that cant get the course + for student in sorted_students_who_can_get_course: + alloc.give(student, course, logger) + agents_who_need_an_item_in_current_iteration.remove(student) # removing the agent from the set (dont worry he will come back in the next round) + map_agent_to_best_item.pop(student, None) # Delete student if exists in the dict + #del map_course_to_students_with_max_bids[course][s] + map_student_to_his_sum_bids[student] -= price + + + #if len(map_course_to_students_with_max_bids[course]) == 0: + # continue + #if len(map_course_to_students_with_max_bids[course]) <= remaining_capacity: + #for student in map_course_to_students_with_max_bids[course]: + # alloc.give(student,course,logger) + # agents_who_need_an_item_in_current_iteration.remove(student) # removing the agent from the set (dont worry he will come back in the next round) + # if student in map_agent_to_best_item: # removing the agent from the max dict + # del map_agent_to_best_item[student] #del dict_by_course[c][s] - else: - sorted_students = sorted(map_course_to_students_with_max_bids[course].keys(), key=lambda name: map_course_to_students_with_max_bids[course][name], reverse=True) #sort the keys by their values (descending order) - price = dict_extra_bids[sorted_students[c_capacity]] #the amount of bids of the first studen that cant get the course - for i in range(c_capacity): - s = sorted_students[i] - alloc.give(s, course, logger) - set_agent.remove(s) # removing the agent from the set (dont worry he will come back in the next round) - if s in map_agent_to_best_item: # removing the agent from the max dict - del map_agent_to_best_item[s] # removing the agent from the max dict - del map_course_to_students_with_max_bids[course][s] - dict_extra_bids[s] -= price - - set_agent = list(dict.fromkeys(alloc.remaining_agents())) #with this random in loop and k passes + # else: + # sorted_students = sorted(map_course_to_students_with_max_bids[course].keys(), key=lambda name: map_course_to_students_with_max_bids[course][name], reverse=True) #sort the keys by their values (descending order) + # price = map_student_to_his_sum_bids[sorted_students[remaining_capacity]] #the amount of bids of the first studen that cant get the course + # for i in range(remaining_capacity): + # s = sorted_students[i] + # alloc.give(s, course, logger) + # set_agent.remove(s) # removing the agent from the set (dont worry he will come back in the next round) + # if s in map_agent_to_best_item: # removing the agent from the max dict + # del map_agent_to_best_item[s] # removing the agent from the max dict + # del map_course_to_students_with_max_bids[course][s] + # map_student_to_his_sum_bids[s] -= price + + # set_agent = list(dict.fromkeys(alloc.remaining_agents())) #with this random in loop and k passes #set_agent = list(dict.fromkeys(agent_order)) #with this the random and k diffrent got ValueError if __name__ == "__main__": diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py index 8fbac0f..c377738 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py @@ -34,25 +34,18 @@ def TTC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger """ explanation_logger.info("\nAlgorithm TTC starts.\n") - agent_order = [] # list of all the instances of agents as many as their items capacities (from the article: sum of Ki) map_agent_to_best_item = {} # dict of the max bids for each agent in specific iteration (from the article: max{i, b}) - - # for agent, capacity in alloc.remaining_agent_capacities.items(): # save the agent number of his needed courses (שומר את הסטודנט מספר פעמים כמספר הקורסים שהוא צריך) - # agent_order.extend([agent] * capacity) - # - # agents_who_need_an_item_in_current_iteration = list(dict.fromkeys(agent_order)) # set of the agents by thier order . each agent appears only once - - max_iterations = max(alloc.remaining_agent_capacities[agent] for agent in alloc.remaining_agents()) + max_iterations = max(alloc.remaining_agent_capacities[agent] for agent in alloc.remaining_agents()) # the amount of courses of student with maximum needed courses for iteration in range(max_iterations): # External loop of algorithm: in each iteration, each student gets 1 seat (if possible). - if len(alloc.remaining_agent_capacities) == 0 or len(alloc.remaining_item_capacities) == 0: # check if all the agents got their courses or there are no more + if len(alloc.remaining_agent_capacities) == 0 or len(alloc.remaining_item_capacities) == 0: # check if all the agents got their courses or there are no more courses with seats break - agents_who_need_an_item_in_current_iteration = set(alloc.remaining_agents()) + agents_who_need_an_item_in_current_iteration = set(alloc.remaining_agents()) # only the agents that still need courses while agents_who_need_an_item_in_current_iteration: # round i # 1. Create the dict map_agent_to_best_item agents_with_no_potential_items = set() for current_agent in agents_who_need_an_item_in_current_iteration: # check the course with the max bids for each agent in the set (who didnt get a course in this round) - potential_items_for_agent = set(alloc.remaining_items()).difference(alloc.bundles[current_agent]) # set of all the courses that have places sub the courses the agent got (under the assumption that if agent doesnt want a course the course got 0 bids automaticlly) - # potential_items_for_agent = alloc.remaining_items_for_agent(current_agent) # set of all the courses that have places sub the courses the agent got (under the assumption that if agent doesnt want a course the course got 0 bids automaticlly) + # potential_items_for_agent = set(alloc.remaining_items()).difference(alloc.bundles[current_agent]) # set of all the courses that have places sub the courses the agent got (under the assumption that if agent doesnt want a course the course got 0 bids automaticlly) + potential_items_for_agent = alloc.remaining_items_for_agent(current_agent) # set of all the courses that have places sub the courses the agent got (under the assumption that if agent doesnt want a course the course got 0 bids automaticlly) if len(potential_items_for_agent) == 0: agents_with_no_potential_items.add(current_agent) else: @@ -100,7 +93,7 @@ def TTC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger # agents_who_need_an_item_in_current_iteration = () #agents_who_need_an_item_in_current_iteration = list(dict.fromkeys(agent_order)) # adding all the agents for the next round - agents_who_need_an_item_in_current_iteration = list(dict.fromkeys(alloc.remaining_agents())) + #agents_who_need_an_item_in_current_iteration = list(dict.fromkeys(alloc.remaining_agents())) if __name__ == "__main__": From d4e1168c2c0905adf0b97fcf543e5059cc7691b2 Mon Sep 17 00:00:00 2001 From: ofekats Date: Thu, 9 May 2024 20:38:18 +0300 Subject: [PATCH 07/42] sp with comments --- .../Optimization_based_Mechanisms/SP.py | 65 +++++-------------- .../Optimization_based_Mechanisms/TTC.py | 40 ++---------- 2 files changed, 22 insertions(+), 83 deletions(-) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py b/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py index 3017cf0..3533b07 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py @@ -31,62 +31,51 @@ def SP_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger explanation_logger.info("\nAlgorithm SP starts.\n") - # agent_order = [] # list of all the instances of agents as many as their items capacities (from the article: sum of Ki) - map_agent_to_best_item = {} # dict of the max bids for each agent in specific iteration (from the article: max{i, b}) - map_course_to_students_with_max_bids = {c: {} for c in alloc.remaining_item_capacities} + map_agent_to_best_item = {} # dict of the max bids for each agent in specific iteration (from the article: max{i, b}) + map_student_to_his_sum_bids = {s: 0 for s in alloc.remaining_agents()} # the amount of bids agent have from all the courses he got before - #for agent, capacity in alloc.remaining_agent_capacities.items(): # save the agent number of his needed courses (שומר את הסטודנט מספר פעמים כמספר הקורסים שהוא צריך) - # agent_order.extend([agent] * capacity) - - #set_agent = list(dict.fromkeys(agent_order)) # set of the agents by thier order . each agent appears only once - - map_student_to_his_sum_bids = {s: 0 for s in alloc.remaining_agents()} - - max_iterations = max(alloc.remaining_agent_capacities[agent] for agent in alloc.remaining_agents()) + max_iterations = max(alloc.remaining_agent_capacities[agent] for agent in alloc.remaining_agents()) # the amount of courses of student with maximum needed courses for iteration in range(max_iterations): # External loop of algorithm: in each iteration, each student gets 1 seat (if possible). if len(alloc.remaining_agent_capacities) == 0 or len(alloc.remaining_item_capacities) == 0: # check if all the agents got their courses or there are no more break - agents_who_need_an_item_in_current_iteration = set(alloc.remaining_agents()) + agents_who_need_an_item_in_current_iteration = set(alloc.remaining_agents()) # only the agents that still need courses while agents_who_need_an_item_in_current_iteration: # round i # 1. Create the dict map_agent_to_best_item agents_with_no_potential_items = set() for current_agent in agents_who_need_an_item_in_current_iteration: # check the course with the max bids for each agent in the set (who didnt get a course in this round) - # potential_items_for_agent = set(alloc.remaining_items()).difference(alloc.bundles[current_agent]) # set of all the courses that have places sub the courses the agent got (under the assumption that if agent doesnt want a course the course got 0 bids automaticlly) - potential_items_for_agent = alloc.remaining_items_for_agent(current_agent) # set of all the courses that have places sub the courses the agent got (under the assumption that if agent doesnt want a course the course got 0 bids automaticlly) + potential_items_for_agent = alloc.remaining_items_for_agent(current_agent) # set of all the courses that have places sub the courses the agent got if len(potential_items_for_agent) == 0: agents_with_no_potential_items.add(current_agent) else: map_agent_to_best_item[current_agent] = max(potential_items_for_agent, key=lambda item: alloc.effective_value(current_agent,item)) # for each agent save the course with the max bids from the potential courses only - for current_agent in agents_with_no_potential_items: + for current_agent in agents_with_no_potential_items: # remove agents that don't need courses logger.info("Agent %s cannot pick any more items: remaining=%s, bundle=%s", current_agent, alloc.remaining_item_capacities, alloc.bundles[current_agent]) alloc.remove_agent_from_loop(current_agent) agents_who_need_an_item_in_current_iteration.remove(current_agent) - #map_student_to_his_sum_bids += [alloc.effective_value(student,course) for student, course in map_student_to_his_sum_bids.items()] - for student, course in map_student_to_his_sum_bids.items(): + # 2. Allocate the remaining seats in each course: + for student, course in map_student_to_his_sum_bids.items(): # update the bids of each student if student in map_agent_to_best_item: - map_student_to_his_sum_bids[student] += alloc.effective_value(student ,course) + map_student_to_his_sum_bids[student] += alloc.effective_value(student, course) - #create dict for each course of student that point on the course and their bids + # create dict for each course of student that point on the course and their bids map_course_to_students_with_max_bids = {course: {student: map_student_to_his_sum_bids[student] for student in map_agent_to_best_item if map_agent_to_best_item[student] == course} for course in alloc.remaining_items()} - #loop on the dict_by_course and for each course give it to as many studens it can + # loop on the dict_by_course and for each course give it to as many students it can for course in map_course_to_students_with_max_bids: if course in alloc.remaining_item_capacities: - - sorted_students_pointing_to_course = sorted( [student for student in map_agent_to_best_item if map_agent_to_best_item[student] == course], key=lambda student: alloc.effective_value(student, course), reverse=True) # sort the keys by their values (descending order) - remaining_capacity = alloc.remaining_item_capacities[course] - sorted_students_who_can_get_course = sorted_students_pointing_to_course[:remaining_capacity] - price = 0 + remaining_capacity = alloc.remaining_item_capacities[course] # the amount of seats left in the current course + sorted_students_who_can_get_course = sorted_students_pointing_to_course[:remaining_capacity] # list of the student that can get the course + price = 0 # the course price (0 if evert student that want the course this round can get it) if len(sorted_students_pointing_to_course) > remaining_capacity: price = map_student_to_his_sum_bids[sorted_students_pointing_to_course[remaining_capacity]] # the amount of bids of the first studen that cant get the course for student in sorted_students_who_can_get_course: @@ -94,32 +83,8 @@ def SP_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger agents_who_need_an_item_in_current_iteration.remove(student) # removing the agent from the set (dont worry he will come back in the next round) map_agent_to_best_item.pop(student, None) # Delete student if exists in the dict #del map_course_to_students_with_max_bids[course][s] - map_student_to_his_sum_bids[student] -= price - - - #if len(map_course_to_students_with_max_bids[course]) == 0: - # continue - #if len(map_course_to_students_with_max_bids[course]) <= remaining_capacity: - #for student in map_course_to_students_with_max_bids[course]: - # alloc.give(student,course,logger) - # agents_who_need_an_item_in_current_iteration.remove(student) # removing the agent from the set (dont worry he will come back in the next round) - # if student in map_agent_to_best_item: # removing the agent from the max dict - # del map_agent_to_best_item[student] - #del dict_by_course[c][s] - # else: - # sorted_students = sorted(map_course_to_students_with_max_bids[course].keys(), key=lambda name: map_course_to_students_with_max_bids[course][name], reverse=True) #sort the keys by their values (descending order) - # price = map_student_to_his_sum_bids[sorted_students[remaining_capacity]] #the amount of bids of the first studen that cant get the course - # for i in range(remaining_capacity): - # s = sorted_students[i] - # alloc.give(s, course, logger) - # set_agent.remove(s) # removing the agent from the set (dont worry he will come back in the next round) - # if s in map_agent_to_best_item: # removing the agent from the max dict - # del map_agent_to_best_item[s] # removing the agent from the max dict - # del map_course_to_students_with_max_bids[course][s] - # map_student_to_his_sum_bids[s] -= price + map_student_to_his_sum_bids[student] -= price # remove the price of the course from the bids of the student who got the course - # set_agent = list(dict.fromkeys(alloc.remaining_agents())) #with this random in loop and k passes - #set_agent = list(dict.fromkeys(agent_order)) #with this the random and k diffrent got ValueError if __name__ == "__main__": import doctest, sys diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py index c377738..cb0dcc4 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py @@ -42,17 +42,16 @@ def TTC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger agents_who_need_an_item_in_current_iteration = set(alloc.remaining_agents()) # only the agents that still need courses while agents_who_need_an_item_in_current_iteration: # round i # 1. Create the dict map_agent_to_best_item - agents_with_no_potential_items = set() - for current_agent in agents_who_need_an_item_in_current_iteration: # check the course with the max bids for each agent in the set (who didnt get a course in this round) - # potential_items_for_agent = set(alloc.remaining_items()).difference(alloc.bundles[current_agent]) # set of all the courses that have places sub the courses the agent got (under the assumption that if agent doesnt want a course the course got 0 bids automaticlly) - potential_items_for_agent = alloc.remaining_items_for_agent(current_agent) # set of all the courses that have places sub the courses the agent got (under the assumption that if agent doesnt want a course the course got 0 bids automaticlly) + agents_with_no_potential_items = set() # agents that don't need courses to be removed later + for current_agent in agents_who_need_an_item_in_current_iteration: # check the course with the max bids for each agent in the set (who didnt get a course in this round) + potential_items_for_agent = alloc.remaining_items_for_agent(current_agent) # set of all the courses that have places sub the courses the agent got if len(potential_items_for_agent) == 0: agents_with_no_potential_items.add(current_agent) else: map_agent_to_best_item[current_agent] = max(potential_items_for_agent, key=lambda item: alloc.effective_value(current_agent, item)) # for each agent save the course with the max bids from the potential courses only - for current_agent in agents_with_no_potential_items: + for current_agent in agents_with_no_potential_items: # remove agents that don't need courses logger.info("Agent %s cannot pick any more items: remaining=%s, bundle=%s", current_agent, alloc.remaining_item_capacities, alloc.bundles[current_agent]) alloc.remove_agent_from_loop(current_agent) @@ -64,38 +63,13 @@ def TTC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger [student for student in map_agent_to_best_item if map_agent_to_best_item[student] == course], key=lambda student: alloc.effective_value(student,course), reverse=True) # sort the keys by their values (descending order) - remaining_capacity = alloc.remaining_item_capacities[course] - sorted_students_who_can_get_course = sorted_students_pointing_to_course[:remaining_capacity] + remaining_capacity = alloc.remaining_item_capacities[course] # the amount of seats left in the current course + sorted_students_who_can_get_course = sorted_students_pointing_to_course[:remaining_capacity] # list of the student that can get the course for student in sorted_students_who_can_get_course: alloc.give(student, course, logger) - agents_who_need_an_item_in_current_iteration.remove(student) # removing the agent from the set (dont worry he will come back in the next round) + agents_who_need_an_item_in_current_iteration.remove(student) # removing the agent from the set (dont worry he will come back in the next round) map_agent_to_best_item.pop(student, None) # Delete student if exists in the dict - - - - # max_val = 0 - # student_with_max_bids = None - # for agent,best_item in map_agent_to_best_item.items(): # checking the agent with the most bids from agents_with_max_item - # if alloc.effective_value(agent,best_item) > max_val: - # max_val = alloc.effective_value(agent,best_item) - # student_with_max_bids = agent - # - # # if max_val == 0: # if agent bids 0 on item he won't get that item - # # return - # if student_with_max_bids is not None and student_with_max_bids in alloc.remaining_agent_capacities: #after we found the agent we want to give him the course, checking if the agent in dict before removing - # if alloc.remaining_agent_capacities[student_with_max_bids] > 0: - # alloc.give(student_with_max_bids, map_agent_to_best_item[student_with_max_bids], logger) #the agent gets the course - # agents_who_need_an_item_in_current_iteration.remove(student_with_max_bids) # removing the agent from the set (dont worry he will come back in the next round) - # if student_with_max_bids in map_agent_to_best_item: # removing the agent from the max dict - # del map_agent_to_best_item[student_with_max_bids] - # else: - # agents_who_need_an_item_in_current_iteration = () - - #agents_who_need_an_item_in_current_iteration = list(dict.fromkeys(agent_order)) # adding all the agents for the next round - #agents_who_need_an_item_in_current_iteration = list(dict.fromkeys(alloc.remaining_agents())) - - if __name__ == "__main__": import doctest From 313d93dc719e0fc110bed5a5a40779a78f28b4ee Mon Sep 17 00:00:00 2001 From: ofekats Date: Thu, 9 May 2024 22:39:02 +0300 Subject: [PATCH 08/42] sp move bids of courses with no potentioal- no longer have seats or have conflicts - not working yet --- .../Optimization_based_Mechanisms/SP.py | 31 ++++++++++++++++--- .../test_SP.py | 15 +-------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py b/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py index 3533b07..c77fbee 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py @@ -34,6 +34,9 @@ def SP_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger map_agent_to_best_item = {} # dict of the max bids for each agent in specific iteration (from the article: max{i, b}) map_student_to_his_sum_bids = {s: 0 for s in alloc.remaining_agents()} # the amount of bids agent have from all the courses he got before + # {'s1': {} , 's2': {} ...} + map_student_to_course_with_no_seats_and_the_bids = {student: {} for student in alloc.remaining_agents()} + max_iterations = max(alloc.remaining_agent_capacities[agent] for agent in alloc.remaining_agents()) # the amount of courses of student with maximum needed courses for iteration in range(max_iterations): # External loop of algorithm: in each iteration, each student gets 1 seat (if possible). if len(alloc.remaining_agent_capacities) == 0 or len(alloc.remaining_item_capacities) == 0: # check if all the agents got their courses or there are no more @@ -43,12 +46,23 @@ def SP_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger # 1. Create the dict map_agent_to_best_item agents_with_no_potential_items = set() for current_agent in agents_who_need_an_item_in_current_iteration: # check the course with the max bids for each agent in the set (who didnt get a course in this round) + + potential_items_for_agent = alloc.remaining_items_for_agent(current_agent) # set of all the courses that have places sub the courses the agent got if len(potential_items_for_agent) == 0: agents_with_no_potential_items.add(current_agent) else: map_agent_to_best_item[current_agent] = max(potential_items_for_agent, - key=lambda item: alloc.effective_value(current_agent,item)) # for each agent save the course with the max bids from the potential courses only + key=lambda item: alloc.effective_value(current_agent, item)) # for each agent save the course with the max bids from the potential courses only + current_course = map_agent_to_best_item[current_agent] + pop_course_that_moved_bids = [] + if current_agent in map_student_to_course_with_no_seats_and_the_bids: + for course_that_no_longer_potential in map_student_to_course_with_no_seats_and_the_bids[current_agent]: + if alloc.effective_value(current_agent, current_course) < map_student_to_course_with_no_seats_and_the_bids[current_agent][course_that_no_longer_potential]: + map_student_to_his_sum_bids[current_agent] += map_student_to_course_with_no_seats_and_the_bids[current_agent][course_that_no_longer_potential] + pop_course_that_moved_bids.append(course_that_no_longer_potential) + for course in pop_course_that_moved_bids: + map_student_to_course_with_no_seats_and_the_bids[current_agent].pop(course, None) for current_agent in agents_with_no_potential_items: # remove agents that don't need courses logger.info("Agent %s cannot pick any more items: remaining=%s, bundle=%s", current_agent, @@ -57,15 +71,15 @@ def SP_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger agents_who_need_an_item_in_current_iteration.remove(current_agent) # 2. Allocate the remaining seats in each course: - for student, course in map_student_to_his_sum_bids.items(): # update the bids of each student - if student in map_agent_to_best_item: - map_student_to_his_sum_bids[student] += alloc.effective_value(student, course) + for student, course in map_agent_to_best_item.items(): # update the bids of each student + map_student_to_his_sum_bids[student] += alloc.effective_value(student, course) # create dict for each course of student that point on the course and their bids map_course_to_students_with_max_bids = {course: {student: map_student_to_his_sum_bids[student] for student in map_agent_to_best_item if map_agent_to_best_item[student] == course} for course in alloc.remaining_items()} + # loop on the dict_by_course and for each course give it to as many students it can for course in map_course_to_students_with_max_bids: if course in alloc.remaining_item_capacities: @@ -78,12 +92,19 @@ def SP_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger price = 0 # the course price (0 if evert student that want the course this round can get it) if len(sorted_students_pointing_to_course) > remaining_capacity: price = map_student_to_his_sum_bids[sorted_students_pointing_to_course[remaining_capacity]] # the amount of bids of the first studen that cant get the course + #map_student_to_his_sum_bids = {s: 0 for s in alloc.remaining_agents()} + if len(sorted_students_pointing_to_course) >= remaining_capacity: + for student in alloc.remaining_agents(): + map_student_to_course_with_no_seats_and_the_bids[student][course] = alloc.effective_value(student, course) for student in sorted_students_who_can_get_course: + for conflict_course in alloc.instance.item_conflicts(course): + map_student_to_course_with_no_seats_and_the_bids[student][conflict_course] = alloc.effective_value(student, conflict_course) alloc.give(student, course, logger) agents_who_need_an_item_in_current_iteration.remove(student) # removing the agent from the set (dont worry he will come back in the next round) map_agent_to_best_item.pop(student, None) # Delete student if exists in the dict - #del map_course_to_students_with_max_bids[course][s] + del map_course_to_students_with_max_bids[course][student] map_student_to_his_sum_bids[student] -= price # remove the price of the course from the bids of the student who got the course + map_student_to_course_with_no_seats_and_the_bids[student].pop(course, None) if __name__ == "__main__": diff --git a/tests/Tests_for_Optimization-based_Mechanisms/test_SP.py b/tests/Tests_for_Optimization-based_Mechanisms/test_SP.py index 0753e3a..af3c93d 100644 --- a/tests/Tests_for_Optimization-based_Mechanisms/test_SP.py +++ b/tests/Tests_for_Optimization-based_Mechanisms/test_SP.py @@ -109,19 +109,6 @@ def test_sub_round_within_round(): assert fairpyx.divide(fairpyx.algorithms.SP_function, instance=instance) == {'s1': ['c3'], 's2': ['c1'], 's3': ['c2']}, "ERROR" - -def test_students_with_the_same_list(): - s1 = {"c1": 55, "c2": 45} - s2 = {"c1": 55, "c2": 45} - instance = fairpyx.Instance( - agent_capacities={"s1": 1, "s2": 1}, - item_capacities={"c1": 1, "c2": 1}, - valuations={"s1": s1, "s2": s2} - ) - - assert fairpyx.divide(fairpyx.algorithms.SP_function, instance=instance) == {'s1': ['c1'], 's2': ['c2']}, "ERROR" - - def test_sub_round_within_sub_round(): s1 = {"c1": 40, "c2": 10, "c3": 20, "c4": 30} s2 = {"c1": 50, "c2": 10, "c3": 15, "c4": 25} @@ -160,7 +147,7 @@ def test_from_the_article(): valuations={"s1": s1, "s2": s2, "s3": s3, "s4": s4} ) - assert fairpyx.divide(fairpyx.algorithms.SP_function, instance=instance) == {'s1': ['c1', 'c3', 'c2'], 's2': ['c3', 'c2', 'c4'], 's3': ['c4', 'c3', 'c5'], 's4': ['c1', 'c2', 'c5']}, "ERROR" + assert fairpyx.divide(fairpyx.algorithms.SP_function, instance=instance) == {'s1': ['c1', 'c2', 'c3'], 's2': ['c2', 'c3', 'c4'], 's3': ['c3', 'c4', 'c5'], 's4': ['c1', 'c2', 'c5']}, "ERROR" def test_different_k_for_students(): From 450c79acc896b08d7c158e69067c2f700cdab439 Mon Sep 17 00:00:00 2001 From: TamarBarIlan Date: Sun, 12 May 2024 09:49:14 +0300 Subject: [PATCH 09/42] fix test_from_the_atricle() --- fairpyx/algorithms/Optimization_based_Mechanisms/SP.py | 2 +- .../Tests_for_Optimization-based_Mechanisms/test_SP.py | 10 +--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py b/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py index c77fbee..300c513 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py @@ -85,7 +85,7 @@ def SP_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger if course in alloc.remaining_item_capacities: sorted_students_pointing_to_course = sorted( [student for student in map_agent_to_best_item if map_agent_to_best_item[student] == course], - key=lambda student: alloc.effective_value(student, course), + key=lambda student: map_student_to_his_sum_bids[student], reverse=True) # sort the keys by their values (descending order) remaining_capacity = alloc.remaining_item_capacities[course] # the amount of seats left in the current course sorted_students_who_can_get_course = sorted_students_pointing_to_course[:remaining_capacity] # list of the student that can get the course diff --git a/tests/Tests_for_Optimization-based_Mechanisms/test_SP.py b/tests/Tests_for_Optimization-based_Mechanisms/test_SP.py index af3c93d..11d26a7 100644 --- a/tests/Tests_for_Optimization-based_Mechanisms/test_SP.py +++ b/tests/Tests_for_Optimization-based_Mechanisms/test_SP.py @@ -38,6 +38,7 @@ def test_small_example(): assert fairpyx.divide(fairpyx.algorithms.SP_function, instance=instance) == {'s1': ['c2', 'c3'], 's2': ['c3', 'c4'], 's3': ['c1', 'c4']}, "ERROR" +# TODO check the algorithem def test_big_example(): s1 = {"c1": 50, "c2": 20, "c3": 11, "c4": 10, "c5": 9} s2 = {"c1": 4, "c2": 5, "c3": 7, "c4": 26, "c5": 60} @@ -61,15 +62,6 @@ def test_big_example(): 's6': ['c1', 'c2', 'c3', 'c5'], 's7': ['c1', 'c3', 'c4', 'c5']}, "ERROR" -def test_same_order_of_course_selection(): - s1 = {"c1": 10, "c2": 20, "c3": 10, "c4": 50, "c5": 10} - s2 = {"c1": 5, "c2": 30, "c3": 3, "c4": 60, "c5": 2} - s3 = {"c1": 20, "c2": 20, "c3": 25, "c4": 25, "c5": 0} - s4 = {"c1": 25, "c2": 25, "c3": 25, "c4": 25, "c5": 0} - s5 = {"c1": 2, "c2": 5, "c3": 2, "c4": 90, "c5": 1} - s6 = {"c1": 12, "c2": 14, "c3": 12, "c4": 50, "c5": 12} - s7 = {"c1": 7, "c2": 5, "c3": 2, "c4": 90, "c5": 1} - instance = fairpyx.Instance( agent_capacities={"s1": 4, "s2": 4, "s3": 4, "s4": 4, "s5": 4, "s6": 4, "s7": 4}, item_capacities={"c1": 6, "c2": 6, "c3": 7, "c4": 3, "c5": 6}, From 247e2814512b038fe6e276dfa62abf319bcdfa55 Mon Sep 17 00:00:00 2001 From: TamarBarIlan Date: Sun, 12 May 2024 10:53:21 +0300 Subject: [PATCH 10/42] SP.py complete --- .../test_SP.py | 35 ++++++------------- 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/tests/Tests_for_Optimization-based_Mechanisms/test_SP.py b/tests/Tests_for_Optimization-based_Mechanisms/test_SP.py index 11d26a7..0bd4ab8 100644 --- a/tests/Tests_for_Optimization-based_Mechanisms/test_SP.py +++ b/tests/Tests_for_Optimization-based_Mechanisms/test_SP.py @@ -41,12 +41,12 @@ def test_small_example(): # TODO check the algorithem def test_big_example(): s1 = {"c1": 50, "c2": 20, "c3": 11, "c4": 10, "c5": 9} - s2 = {"c1": 4, "c2": 5, "c3": 7, "c4": 26, "c5": 60} + s2 = {"c1": 2, "c2": 5, "c3": 7, "c4": 26, "c5": 60} s3 = {"c1": 20, "c2": 30, "c3": 10, "c4": 21, "c5": 19} s4 = {"c1": 24, "c2": 8, "c3": 25, "c4": 17, "c5": 26} - s5 = {"c1": 5, "c2": 2, "c3": 1, "c4": 3, "c5": 90} + s5 = {"c1": 4, "c2": 2, "c3": 1, "c4": 3, "c5": 90} s6 = {"c1": 13, "c2": 15, "c3": 49, "c4": 11, "c5": 12} - s7 = {"c1": 3, "c2": 4, "c3": 6, "c4": 70, "c5": 19} + s7 = {"c1": 1, "c2": 4, "c3": 6, "c4": 70, "c5": 19} instance = fairpyx.Instance( agent_capacities={"s1": 4, "s2": 4, "s3": 4, "s4": 4, "s5": 4, "s6": 4, "s7": 4}, @@ -54,27 +54,14 @@ def test_big_example(): valuations={"s1": s1, "s2": s2, "s3": s3, "s4": s4, "s5": s5, "s6": s6, "s7": s7} ) - assert fairpyx.divide(fairpyx.algorithms.SP_function, instance=instance) == {'s1': ['c1', 'c2', 'c3'], - 's2': ['c2', 'c3', 'c4', 'c5'], - 's3': ['c1', 'c2', 'c4', 'c5'], - 's4': ['c1', 'c2', 'c3', 'c5'], - 's5': ['c1', 'c2', 'c3', 'c5'], - 's6': ['c1', 'c2', 'c3', 'c5'], - 's7': ['c1', 'c3', 'c4', 'c5']}, "ERROR" + assert fairpyx.divide(fairpyx.algorithms.SP_function, instance=instance) == {'s1': ['c1','c2', 'c3', 'c5'], + 's2': ['c1', 'c3', 'c4', 'c5'], + 's3': ['c1', 'c2', 'c4', 'c5'], + 's4': ['c1', 'c2', 'c3', 'c5'], + 's5': ['c1', 'c2', 'c3', 'c5'], + 's6': ['c1', 'c2', 'c3'], + 's7': ['c2', 'c3', 'c4', 'c5']},"ERROR" - instance = fairpyx.Instance( - agent_capacities={"s1": 4, "s2": 4, "s3": 4, "s4": 4, "s5": 4, "s6": 4, "s7": 4}, - item_capacities={"c1": 6, "c2": 6, "c3": 7, "c4": 3, "c5": 6}, - valuations={"s1": s1, "s2": s2, "s3": s3, "s4": s4, "s5": s5, "s6": s6, "s7": s7} - ) - - assert fairpyx.divide(fairpyx.algorithms.SP_function, instance=instance) == {'s1': ['c1', 'c2', 'c3', 'c5'], - 's2': ['c1', 'c3', 'c4', 'c5'], - 's3': ['c1', 'c2', 'c3', 'c5'], - 's4': ['c1', 'c2', 'c3', 'c5'], - 's5': ['c1', 'c2', 'c3', 'c4'], - 's6': ['c1', 'c2', 'c3', 'c5'], - 's7': ['c2', 'c3', 'c4', 'c5']}, "ERROR" def test_optimal_change_result(): @@ -111,7 +98,7 @@ def test_sub_round_within_sub_round(): valuations={"s1": s1, "s2": s2, "s3": s3} ) - assert fairpyx.divide(fairpyx.algorithms.SP_function, instance=instance) == {'s1': ['c1', 'c2'], 's2': ['c4', 'c3'], 's3': ['c3', 'c2']}, "ERROR" + assert fairpyx.divide(fairpyx.algorithms.SP_function, instance=instance) == {'s1': ['c2', 'c3'], 's2': ['c3', 'c4'], 's3': ['c1', 'c2']}, "ERROR" def test_student_bids_the_same_for_different_courses(): From 9d99a81bd409d1fff9cd218cf1ccae6866ca247a Mon Sep 17 00:00:00 2001 From: TamarBarIlan Date: Sun, 12 May 2024 11:14:35 +0300 Subject: [PATCH 11/42] adding comments --- .../Optimization_based_Mechanisms/SP.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py b/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py index 300c513..89c214c 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py @@ -35,7 +35,7 @@ def SP_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger map_student_to_his_sum_bids = {s: 0 for s in alloc.remaining_agents()} # the amount of bids agent have from all the courses he got before # {'s1': {} , 's2': {} ...} - map_student_to_course_with_no_seats_and_the_bids = {student: {} for student in alloc.remaining_agents()} + map_student_to_course_with_no_seats_and_the_bids = {student: {} for student in alloc.remaining_agents()} # map students to courses he can't get for update bids max_iterations = max(alloc.remaining_agent_capacities[agent] for agent in alloc.remaining_agents()) # the amount of courses of student with maximum needed courses for iteration in range(max_iterations): # External loop of algorithm: in each iteration, each student gets 1 seat (if possible). @@ -58,10 +58,11 @@ def SP_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger pop_course_that_moved_bids = [] if current_agent in map_student_to_course_with_no_seats_and_the_bids: for course_that_no_longer_potential in map_student_to_course_with_no_seats_and_the_bids[current_agent]: + # if the algorithm pass a course that the student can't get - add his bids if needed if alloc.effective_value(current_agent, current_course) < map_student_to_course_with_no_seats_and_the_bids[current_agent][course_that_no_longer_potential]: map_student_to_his_sum_bids[current_agent] += map_student_to_course_with_no_seats_and_the_bids[current_agent][course_that_no_longer_potential] - pop_course_that_moved_bids.append(course_that_no_longer_potential) - for course in pop_course_that_moved_bids: + pop_course_that_moved_bids.append(course_that_no_longer_potential) #save the courses should delete + for course in pop_course_that_moved_bids: # remove the courses that the algorithm add the bids map_student_to_course_with_no_seats_and_the_bids[current_agent].pop(course, None) for current_agent in agents_with_no_potential_items: # remove agents that don't need courses @@ -92,19 +93,18 @@ def SP_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger price = 0 # the course price (0 if evert student that want the course this round can get it) if len(sorted_students_pointing_to_course) > remaining_capacity: price = map_student_to_his_sum_bids[sorted_students_pointing_to_course[remaining_capacity]] # the amount of bids of the first studen that cant get the course - #map_student_to_his_sum_bids = {s: 0 for s in alloc.remaining_agents()} - if len(sorted_students_pointing_to_course) >= remaining_capacity: + if len(sorted_students_pointing_to_course) >= remaining_capacity: # if there are equal or more students than capacity for student in alloc.remaining_agents(): - map_student_to_course_with_no_seats_and_the_bids[student][course] = alloc.effective_value(student, course) + map_student_to_course_with_no_seats_and_the_bids[student][course] = alloc.effective_value(student, course) # save the bids for each student for the courses that there aren't more seats for student in sorted_students_who_can_get_course: - for conflict_course in alloc.instance.item_conflicts(course): - map_student_to_course_with_no_seats_and_the_bids[student][conflict_course] = alloc.effective_value(student, conflict_course) + for conflict_course in alloc.instance.item_conflicts(course): #each course that have a conflict with the current course + map_student_to_course_with_no_seats_and_the_bids[student][conflict_course] = alloc.effective_value(student, conflict_course) # save the bids for each student for the courses that have conflict alloc.give(student, course, logger) agents_who_need_an_item_in_current_iteration.remove(student) # removing the agent from the set (dont worry he will come back in the next round) map_agent_to_best_item.pop(student, None) # Delete student if exists in the dict del map_course_to_students_with_max_bids[course][student] map_student_to_his_sum_bids[student] -= price # remove the price of the course from the bids of the student who got the course - map_student_to_course_with_no_seats_and_the_bids[student].pop(course, None) + map_student_to_course_with_no_seats_and_the_bids[student].pop(course, None) # remove the course from the dict because the student get this course if __name__ == "__main__": From 1e50489c679b529ea2a6ca5f089b7cf52c14aba9 Mon Sep 17 00:00:00 2001 From: TamarBarIlan Date: Sun, 12 May 2024 12:52:51 +0300 Subject: [PATCH 12/42] start TTC-O --- .../Optimization_based_Mechanisms/OC.py | 4 +- .../Optimization_based_Mechanisms/SP_O.py | 4 +- .../Optimization_based_Mechanisms/TTC_O.py | 52 +++++++++++++++++-- .../test_SP.py | 1 - 4 files changed, 53 insertions(+), 8 deletions(-) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py b/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py index e91221f..da3fec6 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py @@ -5,11 +5,11 @@ Programmer: Tamar Bar-Ilan, Moriya Ester Ohayon, Ofek Kats """ -from fairpyx import Instance, AllocationBuilder +from fairpyx import Instance, AllocationBuilder, ExplanationLogger import logging logger = logging.getLogger(__name__) -def OC_function(alloc: AllocationBuilder): +def OC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger = ExplanationLogger()): """ Algorethem 5: Allocate the given items to the given agents using the OC protocol. diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py index 7ff2062..475b6dd 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py @@ -4,11 +4,11 @@ Programmer: Tamar Bar-Ilan, Moriya Ester Ohayon, Ofek Kats """ -from fairpyx import Instance, AllocationBuilder +from fairpyx import Instance, AllocationBuilder, ExplanationLogger import logging logger = logging.getLogger(__name__) -def SP_O_function(alloc: AllocationBuilder): +def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger = ExplanationLogger()): """ Algorethem 4: Allocate the given items to the given agents using the SP-O protocol. diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py index b29688f..b040df3 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py @@ -4,11 +4,12 @@ Programmer: Tamar Bar-Ilan, Moriya Ester Ohayon, Ofek Kats """ -from fairpyx import Instance, AllocationBuilder +from fairpyx import Instance, AllocationBuilder, ExplanationLogger import logging +import cvxpy as cp logger = logging.getLogger(__name__) -def TTC_O_function(alloc: AllocationBuilder): +def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger = ExplanationLogger()): """ Algorethem 3: Allocate the given items to the given agents using the TTC-O protocol. @@ -29,7 +30,52 @@ def TTC_O_function(alloc: AllocationBuilder): >>> divide(TTC_O_function, instance=instance) {'s1': ['c2'], 's2': ['c1']} """ - + explanation_logger.info("\nAlgorithm TTC-O starts.\n") + + max_iterations = max(alloc.remaining_agent_capacities[agent] for agent in alloc.remaining_agents()) # the amount of courses of student with maximum needed courses + for iteration in range(max_iterations): + map_agent_to_best_item = {} + for student in alloc.remaining_agents(): + map_agent_to_best_item[student] = max(alloc.remaining_items_for_agent(student), + key=lambda item: alloc.effective_value(student, item)) + obj = cp.Maximize(sum(alloc.effective_value(student, map_agent_to_best_item[student]) for student in map_agent_to_best_item)) + + remaining_item_capacities_in_prev_iteretion = {} + for student in alloc.remaining_agents(): + remaining_item_capacities_in_prev_iteretion[student] = alloc.remaining_agent_capacities[student] + + + constraints = [] + + # condition number 7: + # check that the number of students who took course j does not exceed the capacity of the course + for course in alloc.remaining_items(): + count = 0 + for student in map_agent_to_best_item: + if map_agent_to_best_item[student] == course: + count += 1 + constraints.append(count <= alloc.remaining_item_capacities[course]) + + # condition number 8: + # check that each student receives only one course in each iteration + for student in alloc.remaining_agents(): + constraints.append(remaining_item_capacities_in_prev_iteretion[student] - alloc.remaining_agent_capacities[student] <= 1) + + # condition number 9: + # we don't need + + problem = cp.Problem(obj, constraints=constraints) + result = problem.solve() + + if result is not None: + optimal_value = problem.value + print("Optimal Objective Value:", optimal_value) + # Now you can use this optimal value for further processing + else: + print("Solver failed to find a solution or the problem is infeasible/unbounded.") + + + if __name__ == "__main__": import doctest, sys print(doctest.testmod()) \ No newline at end of file diff --git a/tests/Tests_for_Optimization-based_Mechanisms/test_SP.py b/tests/Tests_for_Optimization-based_Mechanisms/test_SP.py index 0bd4ab8..525d612 100644 --- a/tests/Tests_for_Optimization-based_Mechanisms/test_SP.py +++ b/tests/Tests_for_Optimization-based_Mechanisms/test_SP.py @@ -38,7 +38,6 @@ def test_small_example(): assert fairpyx.divide(fairpyx.algorithms.SP_function, instance=instance) == {'s1': ['c2', 'c3'], 's2': ['c3', 'c4'], 's3': ['c1', 'c4']}, "ERROR" -# TODO check the algorithem def test_big_example(): s1 = {"c1": 50, "c2": 20, "c3": 11, "c4": 10, "c5": 9} s2 = {"c1": 2, "c2": 5, "c3": 7, "c4": 26, "c5": 60} From bc5ada1a91c9e06d4b6d1a6183a992916b2fb217 Mon Sep 17 00:00:00 2001 From: ofekats Date: Mon, 13 May 2024 15:01:25 +0300 Subject: [PATCH 13/42] TTC-O with mat Xij --- .../Optimization_based_Mechanisms/TTC_O.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py index b040df3..cab5546 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py @@ -4,6 +4,8 @@ Programmer: Tamar Bar-Ilan, Moriya Ester Ohayon, Ofek Kats """ +import cvxpy + from fairpyx import Instance, AllocationBuilder, ExplanationLogger import logging import cvxpy as cp @@ -34,6 +36,22 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg max_iterations = max(alloc.remaining_agent_capacities[agent] for agent in alloc.remaining_agents()) # the amount of courses of student with maximum needed courses for iteration in range(max_iterations): + x = cvxpy.Variable((len(alloc.remaining_agents()), len(alloc.remaining_items())), boolean=True) # mat Xij + + i, j = 0, 0 + for student in alloc.remaining_agents(): + for course in alloc.remaining_items_for_agent(student): + sum = x[i][j] * alloc.effective_value(student, course); + j += 1 + j = 0 + i += 1 + + + + obj = cp.Maximize(sum()) + + + map_agent_to_best_item = {} for student in alloc.remaining_agents(): map_agent_to_best_item[student] = max(alloc.remaining_items_for_agent(student), From cb35dbaa31ea0c3ad480ff1590091db84a61d20c Mon Sep 17 00:00:00 2001 From: ofekats Date: Mon, 13 May 2024 18:18:08 +0300 Subject: [PATCH 14/42] TTC-O with new constraints --- .../Optimization_based_Mechanisms/TTC_O.py | 79 ++++++++++++------- 1 file changed, 49 insertions(+), 30 deletions(-) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py index cab5546..fa463ae 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py @@ -36,51 +36,70 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg max_iterations = max(alloc.remaining_agent_capacities[agent] for agent in alloc.remaining_agents()) # the amount of courses of student with maximum needed courses for iteration in range(max_iterations): - x = cvxpy.Variable((len(alloc.remaining_agents()), len(alloc.remaining_items())), boolean=True) # mat Xij + x = cvxpy.Variable((len(alloc.remaining_items()), len(alloc.remaining_agents())), boolean=True) # mat Xij i, j = 0, 0 - for student in alloc.remaining_agents(): - for course in alloc.remaining_items_for_agent(student): - sum = x[i][j] * alloc.effective_value(student, course); + sums = 0 + for course in alloc.remaining_items(): + for student in alloc.remaining_agents(): + sums = x[i, j] * alloc.effective_value(student, course) j += 1 j = 0 i += 1 - - - obj = cp.Maximize(sum()) - - - - map_agent_to_best_item = {} - for student in alloc.remaining_agents(): - map_agent_to_best_item[student] = max(alloc.remaining_items_for_agent(student), - key=lambda item: alloc.effective_value(student, item)) - obj = cp.Maximize(sum(alloc.effective_value(student, map_agent_to_best_item[student]) for student in map_agent_to_best_item)) - - remaining_item_capacities_in_prev_iteretion = {} - for student in alloc.remaining_agents(): - remaining_item_capacities_in_prev_iteretion[student] = alloc.remaining_agent_capacities[student] - + obj = cp.Maximize(sums) constraints = [] # condition number 7: # check that the number of students who took course j does not exceed the capacity of the course + i = 0 + the_number_of_student_fit_the_course = 0 for course in alloc.remaining_items(): - count = 0 - for student in map_agent_to_best_item: - if map_agent_to_best_item[student] == course: - count += 1 - constraints.append(count <= alloc.remaining_item_capacities[course]) + for j in range(len(alloc.remaining_agents())): + the_number_of_student_fit_the_course += x[i, j] + constraints.append(the_number_of_student_fit_the_course <= alloc.remaining_item_capacities[course]) + i += 1 + the_number_of_student_fit_the_course = 0 # condition number 8: # check that each student receives only one course in each iteration - for student in alloc.remaining_agents(): - constraints.append(remaining_item_capacities_in_prev_iteretion[student] - alloc.remaining_agent_capacities[student] <= 1) - - # condition number 9: - # we don't need + the_number_of_courses_a_student_got = 0 + for i in range(len(alloc.remaining_agents())): + for j in range(len(alloc.remaining_items())): + the_number_of_courses_a_student_got += x[i, j] + constraints.append(the_number_of_courses_a_student_got <= 1) + the_number_of_courses_a_student_got = 0 # reset for the next column + + # map_agent_to_best_item = {} + # for student in alloc.remaining_agents(): + # map_agent_to_best_item[student] = max(alloc.remaining_items_for_agent(student), + # key=lambda item: alloc.effective_value(student, item)) + # obj = cp.Maximize(sum(alloc.effective_value(student, map_agent_to_best_item[student]) for student in map_agent_to_best_item)) + # + # remaining_item_capacities_in_prev_iteretion = {} + # for student in alloc.remaining_agents(): + # remaining_item_capacities_in_prev_iteretion[student] = alloc.remaining_agent_capacities[student] + + + + + # # condition number 7: + # # check that the number of students who took course j does not exceed the capacity of the course + # for course in alloc.remaining_items(): + # count = 0 + # for student in map_agent_to_best_item: + # if map_agent_to_best_item[student] == course: + # count += 1 + # constraints.append(count <= alloc.remaining_item_capacities[course]) + # + # # condition number 8: + # # check that each student receives only one course in each iteration + # for student in alloc.remaining_agents(): + # constraints.append(remaining_item_capacities_in_prev_iteretion[student] - alloc.remaining_agent_capacities[student] <= 1) + # + # # condition number 9: + # # we don't need problem = cp.Problem(obj, constraints=constraints) result = problem.solve() From 6fa782f30f8b8e712fba6a22d46021cc355d5330 Mon Sep 17 00:00:00 2001 From: MoriyaEster Date: Sun, 19 May 2024 22:32:38 +0300 Subject: [PATCH 15/42] TTC-O work only in first round --- .../Optimization_based_Mechanisms/TTC_O.py | 85 ++++++------------- 1 file changed, 26 insertions(+), 59 deletions(-) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py index fa463ae..06b0651 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py @@ -38,79 +38,46 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg for iteration in range(max_iterations): x = cvxpy.Variable((len(alloc.remaining_items()), len(alloc.remaining_agents())), boolean=True) # mat Xij - i, j = 0, 0 - sums = 0 - for course in alloc.remaining_items(): - for student in alloc.remaining_agents(): - sums = x[i, j] * alloc.effective_value(student, course) - j += 1 - j = 0 - i += 1 - - obj = cp.Maximize(sums) + objective = cp.Maximize(cp.sum(cp.multiply(x, [[alloc.effective_value(student, course) + for i, course in enumerate(alloc.remaining_items())] + for j, student in enumerate(alloc.remaining_agents())]))) constraints = [] # condition number 7: # check that the number of students who took course j does not exceed the capacity of the course - i = 0 - the_number_of_student_fit_the_course = 0 - for course in alloc.remaining_items(): - for j in range(len(alloc.remaining_agents())): - the_number_of_student_fit_the_course += x[i, j] - constraints.append(the_number_of_student_fit_the_course <= alloc.remaining_item_capacities[course]) - i += 1 - the_number_of_student_fit_the_course = 0 + for i, course in enumerate(alloc.remaining_items()): + constraints.append(cp.sum(x[i, :]) <= alloc.remaining_item_capacities[course]) + # condition number 8: # check that each student receives only one course in each iteration - the_number_of_courses_a_student_got = 0 - for i in range(len(alloc.remaining_agents())): - for j in range(len(alloc.remaining_items())): - the_number_of_courses_a_student_got += x[i, j] - constraints.append(the_number_of_courses_a_student_got <= 1) - the_number_of_courses_a_student_got = 0 # reset for the next column - - # map_agent_to_best_item = {} - # for student in alloc.remaining_agents(): - # map_agent_to_best_item[student] = max(alloc.remaining_items_for_agent(student), - # key=lambda item: alloc.effective_value(student, item)) - # obj = cp.Maximize(sum(alloc.effective_value(student, map_agent_to_best_item[student]) for student in map_agent_to_best_item)) - # - # remaining_item_capacities_in_prev_iteretion = {} - # for student in alloc.remaining_agents(): - # remaining_item_capacities_in_prev_iteretion[student] = alloc.remaining_agent_capacities[student] - - - - - # # condition number 7: - # # check that the number of students who took course j does not exceed the capacity of the course - # for course in alloc.remaining_items(): - # count = 0 - # for student in map_agent_to_best_item: - # if map_agent_to_best_item[student] == course: - # count += 1 - # constraints.append(count <= alloc.remaining_item_capacities[course]) - # - # # condition number 8: - # # check that each student receives only one course in each iteration - # for student in alloc.remaining_agents(): - # constraints.append(remaining_item_capacities_in_prev_iteretion[student] - alloc.remaining_agent_capacities[student] <= 1) - # - # # condition number 9: - # # we don't need - - problem = cp.Problem(obj, constraints=constraints) + for j, student in enumerate(alloc.remaining_agents()): + constraints.append(cp.sum(x[:, j]) <= 1) + + problem = cp.Problem(objective, constraints=constraints) result = problem.solve() + # Check if the optimization problem was successfully solved if result is not None: + # Extract the optimized values of x + x_values = x.value + + # Assign courses based on the optimized values of x + assign_map_course_to_student = {} + for j, student in enumerate(alloc.remaining_agents()): + for i, course in enumerate(alloc.remaining_items()): + if x_values[i, j] == 1: + assign_map_course_to_student[student] = course + + for student, course in assign_map_course_to_student.items(): + alloc.give(student,course) + optimal_value = problem.value - print("Optimal Objective Value:", optimal_value) + explanation_logger.info("Optimal Objective Value:", optimal_value) # Now you can use this optimal value for further processing else: - print("Solver failed to find a solution or the problem is infeasible/unbounded.") - + explanation_logger.info("Solver failed to find a solution or the problem is infeasible/unbounded.") if __name__ == "__main__": From 8378263ea6f8b48c009c7aab9ae973fd1894c2ef Mon Sep 17 00:00:00 2001 From: TamarBarIlan Date: Tue, 21 May 2024 14:55:50 +0300 Subject: [PATCH 16/42] fix TTC-O. can do more than 1 iteration --- = | Bin 0 -> 392 bytes .../Optimization_based_Mechanisms/TTC_O.py | 33 +++++++++++++----- .../test_TTC_O.py | 6 ++-- 3 files changed, 27 insertions(+), 12 deletions(-) create mode 100644 = diff --git a/= b/= new file mode 100644 index 0000000000000000000000000000000000000000..12e498d68eba206d638d45db82a847bbb36d48f6 GIT binary patch literal 392 zcmZ{g%?iRm420(__zpelLBT)No_zw(1zBsY#cI{9e=o0o*@ECfmdz%U$t2nLYoSbA zIbBt%R-vPEMR*nVDo^%;XSfG-uu7~kE3imw7-^_CZUk0QaR|(angVX7o=zaC_Sl@* z2TilauX*oOHC%^g38$@=mb_=?D9C20X7G(|a~R^Z(UaKY*-V~Y3h<3$&{Q`)=={_Q zO-I%yv|TJ0EBS8d@#mUqsEKCyj;a$Xrs{| Date: Tue, 21 May 2024 17:44:59 +0300 Subject: [PATCH 17/42] TTC-O except the random test --- =1.12.0 | 2 + .../Optimization_based_Mechanisms/TTC_O.py | 58 +++++++++++++++---- .../test_TTC_O.py | 20 +++---- 3 files changed, 59 insertions(+), 21 deletions(-) create mode 100644 =1.12.0 diff --git a/=1.12.0 b/=1.12.0 new file mode 100644 index 0000000..41068d0 --- /dev/null +++ b/=1.12.0 @@ -0,0 +1,2 @@ +Requirement already satisfied: scipy in /home/moriyaester/Desktop/Autonomous-Robots/.venv/Scripts/python.exe/lib/python3.10/site-packages (1.11.4) +Requirement already satisfied: numpy<1.28.0,>=1.21.6 in /home/moriyaester/Desktop/Autonomous-Robots/.venv/Scripts/python.exe/lib/python3.10/site-packages (from scipy) (1.26.4) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py index 5146116..4c5dae7 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py @@ -36,20 +36,25 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg max_iterations = max(alloc.remaining_agent_capacities[agent] for agent in alloc.remaining_agents()) # the amount of courses of student with maximum needed courses for iteration in range(max_iterations): - x = cvxpy.Variable((len(alloc.remaining_items()), len(alloc.remaining_agents())), boolean=True) # mat Xij - # objective = cp.Maximize(cp.sum( - # cp.multiply(x, [[alloc.effective_value(student, course) - # for i, course in enumerate(alloc.remaining_items())] - # for j, student in enumerate(alloc.remaining_agents()) - # ]))) + rank_mat = [[0 for _ in range(len(alloc.remaining_agents()))] for _ in range(len(alloc.remaining_items()))] - objective_Zt1 = cp.Maximize(cp.sum([alloc.effective_value(student, course) * x[i,j] + for j, student in enumerate(alloc.remaining_agents()): + map_courses_to_student = alloc.remaining_items_for_agent(student) + sorted_courses = sorted(map_courses_to_student, key=lambda course: alloc.effective_value(student, course), ) + + for i, course in enumerate(alloc.remaining_items()): + if course in sorted_courses: + rank_mat[i][j] = sorted_courses.index(course) + 1 + else: + rank_mat[i][j] = len(sorted_courses) + 1 + + x = cvxpy.Variable((len(alloc.remaining_items()), len(alloc.remaining_agents())), boolean=True) + + objective_Zt1 = cp.Maximize(cp.sum([rank_mat[i][j] * x[i,j] for i, course in enumerate(alloc.remaining_items()) for j, student in enumerate(alloc.remaining_agents()) - if (student, course) not in alloc.remaining_conflicts - ] - )) + if (student, course) not in alloc.remaining_conflicts])) constraints_Zt1 = [] @@ -72,9 +77,40 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg result_Zt1 = problem.solve() # This is the optimal value of program (6)(7)(8)(9). # Write and solve new program for Zt2 (10)(11)(7)(8) + x = cvxpy.Variable((len(alloc.remaining_items()), len(alloc.remaining_agents())), boolean=True) #Is there a func which zero all the matrix? + + objective_Zt2 = cp.Maximize(cp.sum([alloc.effective_value(student, course) * x[i, j] + for i, course in enumerate(alloc.remaining_items()) + for j, student in enumerate(alloc.remaining_agents()) + if (student, course) not in alloc.remaining_conflicts])) + + constraints_Zt2 = [] + + # condition number 7: + # check that the number of students who took course j does not exceed the capacity of the course + for i, course in enumerate(alloc.remaining_items()): + constraints_Zt2.append(cp.sum(x[i, :]) <= alloc.remaining_item_capacities[course]) + + for j, student in enumerate(alloc.remaining_agents()): + if (student, course) in alloc.remaining_conflicts: + constraints_Zt2.append(x[i, j] == 0) + + # condition number 8: + # check that each student receives only one course in each iteration + for j, student in enumerate(alloc.remaining_agents()): + constraints_Zt2.append(cp.sum(x[:, j]) <= 1) + + constraints_Zt2.append(cp.sum([rank_mat[i][j] * x[i, j] + for i, course in enumerate(alloc.remaining_items()) + for j, student in enumerate(alloc.remaining_agents()) + if (student, course) not in alloc.remaining_conflicts + ]) == result_Zt1) + + problem = cp.Problem(objective_Zt2, constraints=constraints_Zt2) + result_Zt2 = problem.solve() # Check if the optimization problem was successfully solved - if result_Zt1 is not None: + if result_Zt2 is not None: # Extract the optimized values of x x_values = x.value diff --git a/tests/Tests_for_Optimization-based_Mechanisms/test_TTC_O.py b/tests/Tests_for_Optimization-based_Mechanisms/test_TTC_O.py index d7e9fbc..15b9494 100644 --- a/tests/Tests_for_Optimization-based_Mechanisms/test_TTC_O.py +++ b/tests/Tests_for_Optimization-based_Mechanisms/test_TTC_O.py @@ -26,9 +26,9 @@ def test_optimal_change_result(): def test_student_get_k_courses(): # in ttc a student didn't get k cources - s1 = {"c1": 50, "c2": 10, "c3": 40} - s2 = {"c1": 45, "c2": 30, "c3": 25} - s3 = {"c1": 49, "c2": 15, "c3": 36} + s1 = {"c1": 50, "c2": 10, "c3": 40} # rank: {"c1": 3, "c2": 1, "c3": 2} his rank:11 our:13 + s2 = {"c1": 45, "c2": 30, "c3": 25} # rank: {"c1": 3, "c2": 2, "c3": 1} his bids:184 our:210 + s3 = {"c1": 49, "c2": 15, "c3": 36} # rank: {"c1": 3, "c2": 1, "c3": 2} instance = fairpyx.Instance( agent_capacities={"s1": 2, "s2": 2, "s3": 2}, item_capacities={"c1": 2, "c2": 2, "c3": 2}, @@ -39,8 +39,8 @@ def test_student_get_k_courses(): # in ttc a student didn't get k cources def test_optimal_improve_cardinal_and_ordinal_results(): - s1 = {"c1": 50, "c2": 30, "c3": 20} # rank: {"c1": 3, "c2": 2, "c3": 1} - s2 = {"c1": 40, "c2": 50, "c3": 10} # rank: {"c1": 2, "c2": 3, "c3": 1} + s1 = {"c1": 50, "c2": 30, "c3": 20} # rank: {"c1": 3, "c2": 2, "c3": 1} round1: rank=9, bids=160 + s2 = {"c1": 40, "c2": 50, "c3": 10} # rank: {"c1": 2, "c2": 3, "c3": 1} round2: rank=4, bids=60 s3 = {"c1": 60, "c2": 10, "c3": 30} # rank: {"c1": 3, "c2": 1, "c3": 2} instance = fairpyx.Instance( agent_capacities={"s1": 2, "s2": 2, "s3": 2}, @@ -48,20 +48,20 @@ def test_optimal_improve_cardinal_and_ordinal_results(): valuations={"s1": s1, "s2": s2, "s3": s3} ) - assert fairpyx.divide(fairpyx.algorithms.TTC_O_function, instance=instance) == {'s1': ['c1', 'c2'], 's2': ['c2', 'c3'], 's3': ['c1', 'c2']}, "ERROR" + assert fairpyx.divide(fairpyx.algorithms.TTC_O_function, instance=instance) == {'s1': ['c1', 'c2'], 's2': ['c2'], 's3': ['c1', 'c3']}, "ERROR" def test_sub_round_within_sub_round(): - s1 = {"c1": 40, "c2": 10, "c3": 20, "c4": 30} - s2 = {"c1": 50, "c2": 10, "c3": 15, "c4": 25} - s3 = {"c1": 60, "c2": 30, "c3": 2, "c4": 8} + s1 = {"c1": 40, "c2": 10, "c3": 20, "c4": 30} # rank: {"c1": 4, "c2": 1, "c3": 2, "c4: 3"} round1: rank=10, bids=110 + s2 = {"c1": 50, "c2": 10, "c3": 15, "c4": 25} # rank: {"c1": 4, "c2": 1, "c3": 2, "c4: 3"} round2: rank=4, bids=35 + s3 = {"c1": 60, "c2": 30, "c3": 2, "c4": 8} # rank: {"c1": 4, "c2": 3, "c3": 1, "c4: 2"} instance = fairpyx.Instance( agent_capacities={"s1": 2, "s2": 2, "s3": 2}, item_capacities={"c1": 1, "c2": 2, "c3": 2, "c4": 1}, valuations={"s1": s1, "s2": s2, "s3": s3} ) - assert fairpyx.divide(fairpyx.algorithms.TTC_O_function, instance=instance) == {'s1': ['c3', 'c4'], 's2': ['c1', 'c2'], 's3': ['c2', 'c3']}, "ERROR" + assert fairpyx.divide(fairpyx.algorithms.TTC_O_function, instance=instance) == {'s1': ['c3', 'c4'], 's2': ['c1', 'c3'], 's3': ['c2']}, "ERROR" def test_optimal_cardinal_utility(): s1 = {"c1": 30, "c2": 35, "c3": 35} From 0a8850d0455ebd790dfbb46076617e2cb2e2b34f Mon Sep 17 00:00:00 2001 From: MoriyaEster Date: Sun, 26 May 2024 23:28:18 +0300 Subject: [PATCH 18/42] start implement SP-O --- .../Optimization_based_Mechanisms/SP_O.py | 78 ++++++++++++++++++- .../test_SP.py | 2 +- .../test_SP_O.py | 2 +- 3 files changed, 79 insertions(+), 3 deletions(-) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py index 475b6dd..9d163ec 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py @@ -4,7 +4,9 @@ Programmer: Tamar Bar-Ilan, Moriya Ester Ohayon, Ofek Kats """ +import cvxpy from fairpyx import Instance, AllocationBuilder, ExplanationLogger +import cvxpy as cp import logging logger = logging.getLogger(__name__) @@ -29,7 +31,81 @@ def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogge >>> divide(SP_O_function, instance=instance) {'s1': ['c2'], 's2': ['c1']} """ - + + explanation_logger.info("\nAlgorithm SP-O starts.\n") + + max_iterations = max(alloc.remaining_agent_capacities[agent] for agent in + alloc.remaining_agents()) # the amount of courses of student with maximum needed courses + for iteration in range(max_iterations): + + rank_mat = [[0 for _ in range(len(alloc.remaining_agents()))] for _ in range(len(alloc.remaining_items()))] + + for j, student in enumerate(alloc.remaining_agents()): + map_courses_to_student = alloc.remaining_items_for_agent(student) + sorted_courses = sorted(map_courses_to_student, key=lambda course: alloc.effective_value(student, course), ) + + for i, course in enumerate(alloc.remaining_items()): + if course in sorted_courses: + rank_mat[i][j] = sorted_courses.index(course) + 1 + else: + rank_mat[i][j] = len(sorted_courses) + 1 + + x = cvxpy.Variable((len(alloc.remaining_items()), len(alloc.remaining_agents())), boolean=True) + + objective_Zt1 = cp.Maximize(cp.sum([rank_mat[i][j] * x[i, j] + for i, course in enumerate(alloc.remaining_items()) + for j, student in enumerate(alloc.remaining_agents()) + if (student, course) not in alloc.remaining_conflicts])) + + constraints_Zt1 = [] + + # condition number 7: + # check that the number of students who took course j does not exceed the capacity of the course + for i, course in enumerate(alloc.remaining_items()): + constraints_Zt1.append(cp.sum(x[i, :]) <= alloc.remaining_item_capacities[course]) + + for j, student in enumerate(alloc.remaining_agents()): + if (student, course) in alloc.remaining_conflicts: + constraints_Zt1.append(x[i, j] == 0) + + # condition number 8: + # check that each student receives only one course in each iteration + for j, student in enumerate(alloc.remaining_agents()): + constraints_Zt1.append(cp.sum(x[:, j]) <= 1) + + problem = cp.Problem(objective_Zt1, constraints=constraints_Zt1) + result_Zt1 = problem.solve() # This is the optimal value of program (6)(7)(8)(9). + + # condition number 12: + D = cvxpy.Variable() + objective_Wt1 = cp.Minimize(result_Zt1*D + + cp.sum(alloc.remaining_item_capacities[course]*p[course] for course in alloc.remaining_items()) + + cp.sum(v[student] for student in alloc.remaining_agents())) + + constraints_Wt1 = [] + + # Check if the optimization problem was successfully solved + if result_Zt1 is not None: + # Extract the optimized values of x + x_values = x.value + + # Assign courses based on the optimized values of x + assign_map_course_to_student = {} + for j, student in enumerate(alloc.remaining_agents()): + for i, course in enumerate(alloc.remaining_items()): + if x_values[i, j] == 1: + assign_map_course_to_student[student] = course + + for student, course in assign_map_course_to_student.items(): + alloc.give(student, course) + + optimal_value = problem.value + explanation_logger.info("Optimal Objective Value:", optimal_value) + # Now you can use this optimal value for further processing + else: + explanation_logger.info("Solver failed to find a solution or the problem is infeasible/unbounded.") + + if __name__ == "__main__": import doctest, sys print(doctest.testmod()) \ No newline at end of file diff --git a/tests/Tests_for_Optimization-based_Mechanisms/test_SP.py b/tests/Tests_for_Optimization-based_Mechanisms/test_SP.py index 525d612..f7b51a2 100644 --- a/tests/Tests_for_Optimization-based_Mechanisms/test_SP.py +++ b/tests/Tests_for_Optimization-based_Mechanisms/test_SP.py @@ -67,7 +67,7 @@ def test_optimal_change_result(): s1 = {"c1": 50, "c2": 49, "c3": 1} s2 = {"c1": 48, "c2": 46, "c3": 6} instance = fairpyx.Instance( - agent_capacities={"s1": 1, "s2": 1, "s3": 1}, + agent_capacities={"s1": 1, "s2": 1}, item_capacities={"c1": 1, "c2": 1, "c3": 1}, valuations={"s1": s1, "s2": s2} ) diff --git a/tests/Tests_for_Optimization-based_Mechanisms/test_SP_O.py b/tests/Tests_for_Optimization-based_Mechanisms/test_SP_O.py index be5dd57..f1dd603 100644 --- a/tests/Tests_for_Optimization-based_Mechanisms/test_SP_O.py +++ b/tests/Tests_for_Optimization-based_Mechanisms/test_SP_O.py @@ -17,7 +17,7 @@ def test_optimal_change_result(): s1 = {"c1": 50, "c2": 49, "c3": 1} s2 = {"c1": 48, "c2": 46, "c3": 6} instance = fairpyx.Instance( - agent_capacities={"s1": 1, "s2": 1, "s3": 1}, + agent_capacities={"s1": 1, "s2": 1}, item_capacities={"c1": 1, "c2": 1, "c3": 1}, valuations={"s1": s1, "s2": s2} ) From 2085d419d5840e1ee11ceedccd38ec4971d50de4 Mon Sep 17 00:00:00 2001 From: TamarBarIlan Date: Tue, 28 May 2024 20:02:42 +0300 Subject: [PATCH 19/42] adding OC.py --- .../Optimization_based_Mechanisms/OC.py | 107 ++++++++++++++++++ .../test_OC.py | 4 +- 2 files changed, 109 insertions(+), 2 deletions(-) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py b/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py index da3fec6..2646710 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py @@ -5,8 +5,11 @@ Programmer: Tamar Bar-Ilan, Moriya Ester Ohayon, Ofek Kats """ +import cvxpy + from fairpyx import Instance, AllocationBuilder, ExplanationLogger import logging +import cvxpy as cp logger = logging.getLogger(__name__) def OC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger = ExplanationLogger()): @@ -30,6 +33,110 @@ def OC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger {'s1': ['c1', 'c3'], 's2': ['c1', 'c2']} """ + explanation_logger.info("\nAlgorithm OC starts.\n") + + x = cvxpy.Variable((len(alloc.remaining_items()), len(alloc.remaining_agents())), boolean=True) + + rank_mat = [[0 for _ in range(len(alloc.remaining_agents()))] for _ in range(len(alloc.remaining_items()))] + + for j, student in enumerate(alloc.remaining_agents()): + map_courses_to_student = alloc.remaining_items_for_agent(student) + sorted_courses = sorted(map_courses_to_student, key=lambda course: alloc.effective_value(student, course), ) + + for i, course in enumerate(alloc.remaining_items()): + if course in sorted_courses: + rank_mat[i][j] = sorted_courses.index(course) + 1 + else: + rank_mat[i][j] = len(sorted_courses) + 1 + + objective_Z1 = cp.Maximize(cp.sum([rank_mat[i][j] * x[i, j] + for i, course in enumerate(alloc.remaining_items()) + for j, student in enumerate(alloc.remaining_agents()) + if (student, course) not in alloc.remaining_conflicts])) + + constraints_Z1 = [] + # condition number 2: + # check that the number of students who took course j does not exceed the capacity of the course + for i, course in enumerate(alloc.remaining_items()): + constraints_Z1.append(cp.sum(x[i, :]) <= alloc.remaining_item_capacities[course]) + + # TODO: check if necessary + # if the course have conflict + for j, student in enumerate(alloc.remaining_agents()): + if (student, course) in alloc.remaining_conflicts: + constraints_Z1.append(x[i, j] == 0) + + # condition number 3: + # Each student can take at most k courses + for j, student in enumerate(alloc.remaining_agents()): + constraints_Z1.append(cp.sum(x[:, j]) <= alloc.remaining_agent_capacities[student]) + + # condition number 4: + # TODO: if necessary because alloc check it + + problem = cp.Problem(objective_Z1, constraints=constraints_Z1) + result_Z1 = problem.solve() + + x = cvxpy.Variable((len(alloc.remaining_items()), len(alloc.remaining_agents())), boolean=True) # Is there a func which zero all the matrix? + + objective_Z2 = cp.Maximize(cp.sum([alloc.effective_value(student, course) * x[i, j] + for i, course in enumerate(alloc.remaining_items()) + for j, student in enumerate(alloc.remaining_agents()) + if (student, course) not in alloc.remaining_conflicts])) + + # condition number 19: + constraints_Z2 = [] + constraints_Z2.append(cp.sum([rank_mat[i][j] * x[i, j] + for i, course in enumerate(alloc.remaining_items()) + for j, student in enumerate(alloc.remaining_agents()) + if (student, course) not in alloc.remaining_conflicts + ]) == result_Z1) + + # condition number 2: + # check that the number of students who took course j does not exceed the capacity of the course + for i, course in enumerate(alloc.remaining_items()): + constraints_Z2.append(cp.sum(x[i, :]) <= alloc.remaining_item_capacities[course]) + + # TODO: check if necessary + # if the course have conflict + for j, student in enumerate(alloc.remaining_agents()): + if (student, course) in alloc.remaining_conflicts: + constraints_Z2.append(x[i, j] == 0) + + # condition number 3: + # Each student can take at most k courses + for j, student in enumerate(alloc.remaining_agents()): + constraints_Z2.append(cp.sum(x[:, j]) <= alloc.remaining_agent_capacities[student]) + + problem = cp.Problem(objective_Z2, constraints=constraints_Z2) + result_Z2 = problem.solve() + + # Check if the optimization problem was successfully solved + if result_Z2 is not None: + # Extract the optimized values of x + x_values = x.value + + # Initialize a dictionary where each student will have an empty list + assign_map_courses_to_student = {student: [] for student in alloc.remaining_agents()} + + # Iterate over students and courses to populate the lists + for j, student in enumerate(alloc.remaining_agents()): + for i, course in enumerate(alloc.remaining_items()): + if x_values[i, j] == 1: + assign_map_courses_to_student[student].append(course) + + # Assign the courses to students based on the dictionary + for student, courses in assign_map_courses_to_student.items(): + for course in courses: + alloc.give(student, course) + + optimal_value = problem.value + explanation_logger.info("Optimal Objective Value:", optimal_value) + # Now you can use this optimal value for further processing + else: + explanation_logger.info("Solver failed to find a solution or the problem is infeasible/unbounded.") + + if __name__ == "__main__": import doctest, sys print(doctest.testmod()) diff --git a/tests/Tests_for_Optimization-based_Mechanisms/test_OC.py b/tests/Tests_for_Optimization-based_Mechanisms/test_OC.py index 77c9bb3..030ae10 100644 --- a/tests/Tests_for_Optimization-based_Mechanisms/test_OC.py +++ b/tests/Tests_for_Optimization-based_Mechanisms/test_OC.py @@ -13,8 +13,8 @@ NUM_OF_RANDOM_INSTANCES=10 def test_optimal_number_of_courses(): - s1 = {"c1": 25, "c2": 25, "c3": 25, "c4": 25} - s2 = {"c1": 20, "c2": 20, "c3": 40, "c4": 20} + s1 = {"c1": 27, "c2": 26, "c3": 24, "c4": 23} #c2 c3 c4 -> 6, 73 c1 c2 c3 -> 9, 77 + s2 = {"c1": 21, "c2": 20, "c3": 40, "c4": 19} #c3 c1 c2 -> 9, 81 c3 c2 c4 -> 6, 79 instance = fairpyx.Instance( agent_capacities={"s1": 3, "s2": 3}, item_capacities={"c1": 1, "c2": 2, "c3": 2, "c4": 1}, From f8b3598626adf6c2b4477297891cb393905f8e72 Mon Sep 17 00:00:00 2001 From: MoriyaEster Date: Thu, 30 May 2024 17:25:46 +0300 Subject: [PATCH 20/42] start sp-o --- .../Optimization_based_Mechanisms/OC.py | 9 +---- .../Optimization_based_Mechanisms/SP_O.py | 35 +++++++++++++++++-- .../Optimization_based_Mechanisms/TTC_O.py | 2 -- .../test_OC.py | 2 +- 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py b/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py index 2646710..dea7148 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py @@ -41,13 +41,11 @@ def OC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger for j, student in enumerate(alloc.remaining_agents()): map_courses_to_student = alloc.remaining_items_for_agent(student) - sorted_courses = sorted(map_courses_to_student, key=lambda course: alloc.effective_value(student, course), ) + sorted_courses = sorted(map_courses_to_student, key=lambda course: alloc.effective_value(student, course)) for i, course in enumerate(alloc.remaining_items()): if course in sorted_courses: rank_mat[i][j] = sorted_courses.index(course) + 1 - else: - rank_mat[i][j] = len(sorted_courses) + 1 objective_Z1 = cp.Maximize(cp.sum([rank_mat[i][j] * x[i, j] for i, course in enumerate(alloc.remaining_items()) @@ -60,7 +58,6 @@ def OC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger for i, course in enumerate(alloc.remaining_items()): constraints_Z1.append(cp.sum(x[i, :]) <= alloc.remaining_item_capacities[course]) - # TODO: check if necessary # if the course have conflict for j, student in enumerate(alloc.remaining_agents()): if (student, course) in alloc.remaining_conflicts: @@ -71,9 +68,6 @@ def OC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger for j, student in enumerate(alloc.remaining_agents()): constraints_Z1.append(cp.sum(x[:, j]) <= alloc.remaining_agent_capacities[student]) - # condition number 4: - # TODO: if necessary because alloc check it - problem = cp.Problem(objective_Z1, constraints=constraints_Z1) result_Z1 = problem.solve() @@ -97,7 +91,6 @@ def OC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger for i, course in enumerate(alloc.remaining_items()): constraints_Z2.append(cp.sum(x[i, :]) <= alloc.remaining_item_capacities[course]) - # TODO: check if necessary # if the course have conflict for j, student in enumerate(alloc.remaining_agents()): if (student, course) in alloc.remaining_conflicts: diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py index 9d163ec..7515a16 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py @@ -78,12 +78,43 @@ def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogge # condition number 12: D = cvxpy.Variable() + p = cvxpy.Variable(len(alloc.remaining_items())) + v = cvxpy.Variable(len(alloc.remaining_agents())) objective_Wt1 = cp.Minimize(result_Zt1*D - + cp.sum(alloc.remaining_item_capacities[course]*p[course] for course in alloc.remaining_items()) - + cp.sum(v[student] for student in alloc.remaining_agents())) + + cp.sum(alloc.remaining_item_capacities[course]*p[j] for j, course in enumerate(alloc.remaining_items())) + + cp.sum(v[i] for i, student in enumerate(alloc.remaining_agents()))) constraints_Wt1 = [] + # condition number 13 + 14: + for j, course in enumerate(alloc.remaining_items()): + for i, student in enumerate(alloc.remaining_agents()): + constraints_Wt1.append(p[j]+v[i]+rank_mat[i][j]*D >= alloc.effective_value(student,course)) + constraints_Wt1.append(p[j] >= 0) + constraints_Wt1.append(v[i] >= 0) + + problem = cp.Problem(objective_Wt1, constraints=constraints_Wt1) + result_Wt1 = problem.solve() # This is the optimal value of program (12)(13)(14). + + objective_Wt2 = cp.Minimize(cp.sum(alloc.remaining_item_capacities[course]* p[j] + for j, course in enumerate(alloc.remaining_items()))) + + constraints_Wt2 = [] + + + constraints_Wt2.append(result_Zt1*D + + cp.sum(alloc.remaining_item_capacities[course]* p[j] + for j, course in enumerate(alloc.remaining_items())) + +cp.sum(v[i] for i, student in enumerate(alloc.remaining_agents())) == result_Wt1) + + for j in range(len(alloc.remaining_items())): + for i in range(len(alloc.remaining_agents())): + constraints_Wt2.append(p[j] >= 0) + constraints_Wt2.append(v[i] >= 0) + + problem = cp.Problem(objective_Wt2, constraints=constraints_Wt2) + result_Wt2 = problem.solve() # This is the optimal price + # Check if the optimization problem was successfully solved if result_Zt1 is not None: # Extract the optimized values of x diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py index 4c5dae7..1a0c334 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py @@ -46,8 +46,6 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg for i, course in enumerate(alloc.remaining_items()): if course in sorted_courses: rank_mat[i][j] = sorted_courses.index(course) + 1 - else: - rank_mat[i][j] = len(sorted_courses) + 1 x = cvxpy.Variable((len(alloc.remaining_items()), len(alloc.remaining_agents())), boolean=True) diff --git a/tests/Tests_for_Optimization-based_Mechanisms/test_OC.py b/tests/Tests_for_Optimization-based_Mechanisms/test_OC.py index 030ae10..76d568f 100644 --- a/tests/Tests_for_Optimization-based_Mechanisms/test_OC.py +++ b/tests/Tests_for_Optimization-based_Mechanisms/test_OC.py @@ -26,7 +26,7 @@ def test_optimal_change_result(): s1 = {"c1": 50, "c2": 49, "c3": 1} s2 = {"c1": 48, "c2": 46, "c3": 6} instance = fairpyx.Instance( - agent_capacities={"s1": 1, "s2": 1, "s3": 1}, + agent_capacities={"s1": 1, "s2": 1}, item_capacities={"c1": 1, "c2": 1, "c3": 1}, valuations={"s1": s1, "s2": s2} ) From 5d9bfe957e4d9aa1ef4f6e71d45959b3c7dcc460 Mon Sep 17 00:00:00 2001 From: MoriyaEster Date: Tue, 4 Jun 2024 10:28:03 +0300 Subject: [PATCH 21/42] loogers --- .../Optimization_based_Mechanisms/SP.py | 54 +++++++++++++++---- .../Optimization_based_Mechanisms/TTC.py | 20 +++++-- 2 files changed, 60 insertions(+), 14 deletions(-) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py b/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py index 89c214c..6a8c395 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py @@ -5,6 +5,9 @@ Programmer: Tamar Bar-Ilan, Moriya Ester Ohayon, Ofek Kats """ from fairpyx import Instance, AllocationBuilder, ExplanationLogger +import numpy as np +import fairpyx + import logging logger = logging.getLogger(__name__) @@ -18,14 +21,14 @@ def SP_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger the fair course allocation problem(CAP). - >>> from fairpyx.adaptors import divide - >>> s1 = {"c1": 50, "c2": 49, "c3": 1} - >>> s2 = {"c1": 48, "c2": 46, "c3": 6} - >>> agent_capacities = {"s1": 1, "s2": 1} # 2 seats required - >>> course_capacities = {"c1": 1, "c2": 1, "c3": 1} # 3 seats available - >>> valuations = {"s1": s1, "s2": s2} - >>> instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) - >>> divide(SP_function, instance=instance) + #>>> from fairpyx.adaptors import divide + #>>> s1 = {"c1": 50, "c2": 49, "c3": 1} + #>>> s2 = {"c1": 48, "c2": 46, "c3": 6} + #>>> agent_capacities = {"s1": 1, "s2": 1} # 2 seats required + #>>> course_capacities = {"c1": 1, "c2": 1, "c3": 1} # 3 seats available + #>>> valuations = {"s1": s1, "s2": s2} + #>>> instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) + #>>> divide(SP_function, instance=instance) {'s1': ['c1'], 's2': ['c2']} """ @@ -38,8 +41,11 @@ def SP_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger map_student_to_course_with_no_seats_and_the_bids = {student: {} for student in alloc.remaining_agents()} # map students to courses he can't get for update bids max_iterations = max(alloc.remaining_agent_capacities[agent] for agent in alloc.remaining_agents()) # the amount of courses of student with maximum needed courses + logger.info("Max iterations: %d", max_iterations) for iteration in range(max_iterations): # External loop of algorithm: in each iteration, each student gets 1 seat (if possible). + logger.info("Iteration number: %d", iteration+1) if len(alloc.remaining_agent_capacities) == 0 or len(alloc.remaining_item_capacities) == 0: # check if all the agents got their courses or there are no more + logger.info("There are no more agents (%d) or items(%d) ",len(alloc.remaining_agent_capacities), len(alloc.remaining_item_capacities)) break agents_who_need_an_item_in_current_iteration = set(alloc.remaining_agents()) # only the agents that still need courses while agents_who_need_an_item_in_current_iteration: # round i @@ -60,7 +66,9 @@ def SP_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger for course_that_no_longer_potential in map_student_to_course_with_no_seats_and_the_bids[current_agent]: # if the algorithm pass a course that the student can't get - add his bids if needed if alloc.effective_value(current_agent, current_course) < map_student_to_course_with_no_seats_and_the_bids[current_agent][course_that_no_longer_potential]: + #logger.info(f'map_student_to_his_sum_bids66{map_student_to_his_sum_bids}') map_student_to_his_sum_bids[current_agent] += map_student_to_course_with_no_seats_and_the_bids[current_agent][course_that_no_longer_potential] + #logger.info(f'map_student_to_his_sum_bids68{map_student_to_his_sum_bids}') pop_course_that_moved_bids.append(course_that_no_longer_potential) #save the courses should delete for course in pop_course_that_moved_bids: # remove the courses that the algorithm add the bids map_student_to_course_with_no_seats_and_the_bids[current_agent].pop(course, None) @@ -73,7 +81,9 @@ def SP_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger # 2. Allocate the remaining seats in each course: for student, course in map_agent_to_best_item.items(): # update the bids of each student + #logger.info(f'map_student_to_his_sum_bids81{map_student_to_his_sum_bids}') map_student_to_his_sum_bids[student] += alloc.effective_value(student, course) + #logger.info(f'map_student_to_his_sum_bids83{map_student_to_his_sum_bids}') # create dict for each course of student that point on the course and their bids map_course_to_students_with_max_bids = {course: @@ -93,12 +103,19 @@ def SP_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger price = 0 # the course price (0 if evert student that want the course this round can get it) if len(sorted_students_pointing_to_course) > remaining_capacity: price = map_student_to_his_sum_bids[sorted_students_pointing_to_course[remaining_capacity]] # the amount of bids of the first studen that cant get the course + logger.info("The price of course %s is:%d",course,price) if len(sorted_students_pointing_to_course) >= remaining_capacity: # if there are equal or more students than capacity for student in alloc.remaining_agents(): + if student in sorted_students_pointing_to_course[remaining_capacity:]: + continue map_student_to_course_with_no_seats_and_the_bids[student][course] = alloc.effective_value(student, course) # save the bids for each student for the courses that there aren't more seats for student in sorted_students_who_can_get_course: for conflict_course in alloc.instance.item_conflicts(course): #each course that have a conflict with the current course + if student in sorted_students_pointing_to_course[remaining_capacity:]: + continue map_student_to_course_with_no_seats_and_the_bids[student][conflict_course] = alloc.effective_value(student, conflict_course) # save the bids for each student for the courses that have conflict + logger.info(f'map_student_to_his_sum_bids{map_student_to_his_sum_bids}') + logger.info("Student %s suggest %d bids for course %s", student,map_student_to_his_sum_bids[student], course) alloc.give(student, course, logger) agents_who_need_an_item_in_current_iteration.remove(student) # removing the agent from the set (dont worry he will come back in the next round) map_agent_to_best_item.pop(student, None) # Delete student if exists in the dict @@ -108,5 +125,22 @@ def SP_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger if __name__ == "__main__": - import doctest, sys - print(doctest.testmod()) \ No newline at end of file + #import doctest, sys + #print(doctest.testmod()) + + logger.addHandler(logging.StreamHandler()) + logger.setLevel(logging.INFO) + + from fairpyx.adaptors import divide + for i in range(10): + np.random.seed(i) + instance = Instance.random_uniform( + num_of_agents=10, num_of_items=5, normalized_sum_of_values=100, + agent_capacity_bounds=[2,4], + item_capacity_bounds=[2,5], + item_base_value_bounds=[1,1000], + item_subjective_ratio_bounds=[0.5, 1.5] + ) + allocation = divide(SP_function, instance=instance) + fairpyx.validate_allocation(instance, allocation, title=f"Seed {i}, SP_function") + divide(SP_function, instance=instance) \ No newline at end of file diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py index cb0dcc4..6bd0f89 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py @@ -11,8 +11,6 @@ import logging logger = logging.getLogger(__name__) -def map_agent_to_best_item(alloc:AllocationBuilder): - pass def TTC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger = ExplanationLogger()): """ @@ -21,7 +19,6 @@ def TTC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger TTC (top trading-cycle) assigns one course in each round to each student, the winning students are defined based on the students’ bid values. :param alloc: an allocation builder, which tracks the allocation and the remaining capacity for items and agents of the fair course allocation problem(CAP). - >>> from fairpyx.adaptors import divide >>> s1 = {"c1": 50, "c2": 49, "c3": 1} >>> s2 = {"c1": 48, "c2": 46, "c3": 6} @@ -36,8 +33,11 @@ def TTC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger map_agent_to_best_item = {} # dict of the max bids for each agent in specific iteration (from the article: max{i, b}) max_iterations = max(alloc.remaining_agent_capacities[agent] for agent in alloc.remaining_agents()) # the amount of courses of student with maximum needed courses + logger.info("Max iterations: %d", max_iterations) for iteration in range(max_iterations): # External loop of algorithm: in each iteration, each student gets 1 seat (if possible). + logger.info("Iteration number: %d", iteration+1) if len(alloc.remaining_agent_capacities) == 0 or len(alloc.remaining_item_capacities) == 0: # check if all the agents got their courses or there are no more courses with seats + logger.info("There are no more agents (%d) or items(%d) ",len(alloc.remaining_agent_capacities), len(alloc.remaining_item_capacities)) break agents_who_need_an_item_in_current_iteration = set(alloc.remaining_agents()) # only the agents that still need courses while agents_who_need_an_item_in_current_iteration: # round i @@ -72,7 +72,19 @@ def TTC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger if __name__ == "__main__": import doctest - print("\n", doctest.testmod(), "\n") + #logger.addHandler(logging.StreamHandler()) + #logger.setLevel(logging.INFO) + + #from fairpyx.adaptors import divide + #s1 = {"c1": 50, "c2": 49, "c3": 1} + #s2 = {"c1": 48, "c2": 46, "c3": 6} + #agent_capacities = {"s1": 1, "s2": 1} # 2 seats required + #course_capacities = {"c1": 1, "c2": 1, "c3": 1} # 3 seats available + #valuations = {"s1": s1, "s2": s2} + #instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) + #divide(TTC_function, instance=instance) + + From ad8dc1a1607e7ebcecbcafbf36a48201314ce34f Mon Sep 17 00:00:00 2001 From: MoriyaEster Date: Tue, 4 Jun 2024 14:50:03 +0300 Subject: [PATCH 22/42] looger for sp and ttc-o --- .../Optimization_based_Mechanisms/SP.py | 58 ++++++------- .../Optimization_based_Mechanisms/TTC_O.py | 86 ++++++++++++------- 2 files changed, 84 insertions(+), 60 deletions(-) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py b/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py index 6a8c395..6c44d76 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py @@ -21,14 +21,14 @@ def SP_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger the fair course allocation problem(CAP). - #>>> from fairpyx.adaptors import divide - #>>> s1 = {"c1": 50, "c2": 49, "c3": 1} - #>>> s2 = {"c1": 48, "c2": 46, "c3": 6} - #>>> agent_capacities = {"s1": 1, "s2": 1} # 2 seats required - #>>> course_capacities = {"c1": 1, "c2": 1, "c3": 1} # 3 seats available - #>>> valuations = {"s1": s1, "s2": s2} - #>>> instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) - #>>> divide(SP_function, instance=instance) + >>> from fairpyx.adaptors import divide + >>> s1 = {"c1": 50, "c2": 49, "c3": 1} + >>> s2 = {"c1": 48, "c2": 46, "c3": 6} + >>> agent_capacities = {"s1": 1, "s2": 1} # 2 seats required + >>> course_capacities = {"c1": 1, "c2": 1, "c3": 1} # 3 seats available + >>> valuations = {"s1": s1, "s2": s2} + >>> instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) + >>> divide(SP_function, instance=instance) {'s1': ['c1'], 's2': ['c2']} """ @@ -114,8 +114,8 @@ def SP_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger if student in sorted_students_pointing_to_course[remaining_capacity:]: continue map_student_to_course_with_no_seats_and_the_bids[student][conflict_course] = alloc.effective_value(student, conflict_course) # save the bids for each student for the courses that have conflict - logger.info(f'map_student_to_his_sum_bids{map_student_to_his_sum_bids}') - logger.info("Student %s suggest %d bids for course %s", student,map_student_to_his_sum_bids[student], course) + #logger.info(f'map_student_to_his_sum_bids{map_student_to_his_sum_bids}') + #logger.info("Student %s suggest %d bids for course %s", student,map_student_to_his_sum_bids[student], course) alloc.give(student, course, logger) agents_who_need_an_item_in_current_iteration.remove(student) # removing the agent from the set (dont worry he will come back in the next round) map_agent_to_best_item.pop(student, None) # Delete student if exists in the dict @@ -125,22 +125,22 @@ def SP_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger if __name__ == "__main__": - #import doctest, sys - #print(doctest.testmod()) - - logger.addHandler(logging.StreamHandler()) - logger.setLevel(logging.INFO) - - from fairpyx.adaptors import divide - for i in range(10): - np.random.seed(i) - instance = Instance.random_uniform( - num_of_agents=10, num_of_items=5, normalized_sum_of_values=100, - agent_capacity_bounds=[2,4], - item_capacity_bounds=[2,5], - item_base_value_bounds=[1,1000], - item_subjective_ratio_bounds=[0.5, 1.5] - ) - allocation = divide(SP_function, instance=instance) - fairpyx.validate_allocation(instance, allocation, title=f"Seed {i}, SP_function") - divide(SP_function, instance=instance) \ No newline at end of file + import doctest, sys + print(doctest.testmod()) + + #logger.addHandler(logging.StreamHandler()) + #logger.setLevel(logging.INFO) + + #from fairpyx.adaptors import divide + #for i in range(10): + # np.random.seed(i) + # instance = Instance.random_uniform( + # num_of_agents=10, num_of_items=5, normalized_sum_of_values=100, + # agent_capacity_bounds=[2,4], + # item_capacity_bounds=[2,5], + # item_base_value_bounds=[1,1000], + # item_subjective_ratio_bounds=[0.5, 1.5] + # ) + # allocation = divide(SP_function, instance=instance) + # fairpyx.validate_allocation(instance, allocation, title=f"Seed {i}, SP_function") + #divide(SP_function, instance=instance) \ No newline at end of file diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py index 1a0c334..be999f4 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py @@ -11,6 +11,23 @@ import cvxpy as cp logger = logging.getLogger(__name__) + +# check that the number of students who took course j does not exceed the capacity of the course +def conditionNumber7(constraints, var, alloc): + for i, course in enumerate(alloc.remaining_items()): + constraints.append(cp.sum(var[i, :]) <= alloc.remaining_item_capacities[course]) + + for j, student in enumerate(alloc.remaining_agents()): + if (student, course) in alloc.remaining_conflicts: + constraints.append(var[i, j] == 0) + + +# check that each student receives only one course in each iteration +def conditionNumber8(constraints, var, alloc): + for j, student in enumerate(alloc.remaining_agents()): + constraints.append(cp.sum(var[:, j]) <= 1) + + def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger = ExplanationLogger()): """ Algorethem 3: Allocate the given items to the given agents using the TTC-O protocol. @@ -22,20 +39,22 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg the fair course allocation problem(CAP). - >>> from fairpyx.adaptors import divide - >>> s1 = {"c1": 50, "c2": 49, "c3": 1} - >>> s2 = {"c1": 48, "c2": 46, "c3": 6} - >>> agent_capacities = {"s1": 1, "s2": 1} # 2 seats required - >>> course_capacities = {"c1": 1, "c2": 1, "c3": 1} # 3 seats available - >>> valuations = {"s1": s1, "s2": s2} - >>> instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) - >>> divide(TTC_O_function, instance=instance) + #>>> from fairpyx.adaptors import divide + #>>> s1 = {"c1": 50, "c2": 49, "c3": 1} + #>>> s2 = {"c1": 48, "c2": 46, "c3": 6} + #>>> agent_capacities = {"s1": 1, "s2": 1} # 2 seats required + #>>> course_capacities = {"c1": 1, "c2": 1, "c3": 1} # 3 seats available + #>>> valuations = {"s1": s1, "s2": s2} + #>>> instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) + #>>> divide(TTC_O_function, instance=instance) {'s1': ['c2'], 's2': ['c1']} """ explanation_logger.info("\nAlgorithm TTC-O starts.\n") max_iterations = max(alloc.remaining_agent_capacities[agent] for agent in alloc.remaining_agents()) # the amount of courses of student with maximum needed courses + logger.info("Max iterations: %d", max_iterations) for iteration in range(max_iterations): + logger.info("Iteration number: %d", iteration+1) rank_mat = [[0 for _ in range(len(alloc.remaining_agents()))] for _ in range(len(alloc.remaining_items()))] @@ -47,6 +66,8 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg if course in sorted_courses: rank_mat[i][j] = sorted_courses.index(course) + 1 + logger.info("Rank matrix: %s", rank_mat) + x = cvxpy.Variable((len(alloc.remaining_items()), len(alloc.remaining_agents())), boolean=True) objective_Zt1 = cp.Maximize(cp.sum([rank_mat[i][j] * x[i,j] @@ -57,22 +78,15 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg constraints_Zt1 = [] # condition number 7: - # check that the number of students who took course j does not exceed the capacity of the course - for i, course in enumerate(alloc.remaining_items()): - constraints_Zt1.append(cp.sum(x[i, :]) <= alloc.remaining_item_capacities[course]) - - for j, student in enumerate(alloc.remaining_agents()): - if (student, course) in alloc.remaining_conflicts: - constraints_Zt1.append(x[i, j] == 0) - + conditionNumber7(constraints_Zt1, x, alloc) # condition number 8: - # check that each student receives only one course in each iteration - for j, student in enumerate(alloc.remaining_agents()): - constraints_Zt1.append(cp.sum(x[:, j]) <= 1) + conditionNumber8(constraints_Zt1, x, alloc) problem = cp.Problem(objective_Zt1, constraints=constraints_Zt1) result_Zt1 = problem.solve() # This is the optimal value of program (6)(7)(8)(9). + logger.info("result_Zt1 - the optimum ranking: %d", result_Zt1) + # Write and solve new program for Zt2 (10)(11)(7)(8) x = cvxpy.Variable((len(alloc.remaining_items()), len(alloc.remaining_agents())), boolean=True) #Is there a func which zero all the matrix? @@ -85,18 +99,10 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg constraints_Zt2 = [] # condition number 7: - # check that the number of students who took course j does not exceed the capacity of the course - for i, course in enumerate(alloc.remaining_items()): - constraints_Zt2.append(cp.sum(x[i, :]) <= alloc.remaining_item_capacities[course]) - - for j, student in enumerate(alloc.remaining_agents()): - if (student, course) in alloc.remaining_conflicts: - constraints_Zt2.append(x[i, j] == 0) + conditionNumber7(constraints_Zt2, x, alloc) # condition number 8: - # check that each student receives only one course in each iteration - for j, student in enumerate(alloc.remaining_agents()): - constraints_Zt2.append(cp.sum(x[:, j]) <= 1) + conditionNumber8(constraints_Zt2, x, alloc) constraints_Zt2.append(cp.sum([rank_mat[i][j] * x[i, j] for i, course in enumerate(alloc.remaining_items()) @@ -106,11 +112,14 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg problem = cp.Problem(objective_Zt2, constraints=constraints_Zt2) result_Zt2 = problem.solve() + logger.info("result_Zt2 - the optimum bids: %d", result_Zt2) + # Check if the optimization problem was successfully solved if result_Zt2 is not None: # Extract the optimized values of x x_values = x.value + logger.info("x_values - the optimum allocation: %s", x_values) # Assign courses based on the optimized values of x assign_map_course_to_student = {} @@ -130,5 +139,20 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg if __name__ == "__main__": - import doctest, sys - print(doctest.testmod()) \ No newline at end of file + #import doctest + #print(doctest.testmod()) + + logger.addHandler(logging.StreamHandler()) + logger.setLevel(logging.INFO) + + from fairpyx.adaptors import divide + + s1 = {"c1": 50, "c2": 10, "c3": 40} # rank: {"c1": 3, "c2": 1, "c3": 2} his rank:11 our:13 + s2 = {"c1": 45, "c2": 30, "c3": 25} # rank: {"c1": 3, "c2": 2, "c3": 1} his bids:184 our:210 + s3 = {"c1": 49, "c2": 15, "c3": 36} # rank: {"c1": 3, "c2": 1, "c3": 2} + instance = Instance( + agent_capacities={"s1": 2, "s2": 2, "s3": 2}, + item_capacities={"c1": 2, "c2": 2, "c3": 2}, + valuations={"s1": s1, "s2": s2, "s3": s3} + ) + divide(TTC_O_function, instance=instance) \ No newline at end of file From 8826538be208f8b46c56b9d9fd024c109d27d647 Mon Sep 17 00:00:00 2001 From: MoriyaEster Date: Tue, 4 Jun 2024 16:39:56 +0300 Subject: [PATCH 23/42] logger for all and outer functions --- .../Optimization_based_Mechanisms/OC.py | 121 ++++++++++------- .../Optimization_based_Mechanisms/SP_O.py | 120 +++++++++++------ .../Optimization_based_Mechanisms/TTC_O.py | 123 ++++++++++-------- .../Optimization_based_Mechanisms/__init__.py | 1 + .../optimal_functions.py | 40 ++++++ fairpyx/algorithms/__init__.py | 3 +- .../test_TTC_O.py | 2 +- 7 files changed, 265 insertions(+), 145 deletions(-) create mode 100644 fairpyx/algorithms/Optimization_based_Mechanisms/optimal_functions.py diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py b/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py index dea7148..2537ceb 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py @@ -12,6 +12,44 @@ import cvxpy as cp logger = logging.getLogger(__name__) +# check that the number of students who took course j does not exceed the capacity of the course +def notExceedtheCapacity(constraints, var, alloc): + for j, course in enumerate(alloc.remaining_items()): + constraints.append(cp.sum(var[j, :]) <= alloc.remaining_item_capacities[course]) + + for i, student in enumerate(alloc.remaining_agents()): + if (student, course) in alloc.remaining_conflicts: + constraints.append(var[j, i] == 0) + + +# check that each student receives only one course in each iteration +def numberOfCourses(constraints, var, alloc, less_then): + for i, student in enumerate(alloc.remaining_agents()): + if less_then == 1: + constraints.append(cp.sum(var[:, i]) <= less_then) + else: + constraints.append(cp.sum(var[:, i]) <= less_then[student]) + +def alloctions(alloc, var, logger): + # Extract the optimized values of x + x_values = var.value + logger.info("x_values - the optimum allocation: %s", x_values) + + # Initialize a dictionary where each student will have an empty list + assign_map_courses_to_student = {student: [] for student in alloc.remaining_agents()} + + # Iterate over students and courses to populate the lists + for i, student in enumerate(alloc.remaining_agents()): + for j, course in enumerate(alloc.remaining_items()): + if x_values[j, i] == 1: + assign_map_courses_to_student[student].append(course) + + # Assign the courses to students based on the dictionary + for student, courses in assign_map_courses_to_student.items(): + for course in courses: + alloc.give(student, course) + + def OC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger = ExplanationLogger()): """ Algorethem 5: Allocate the given items to the given agents using the OC protocol. @@ -39,89 +77,63 @@ def OC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger rank_mat = [[0 for _ in range(len(alloc.remaining_agents()))] for _ in range(len(alloc.remaining_items()))] - for j, student in enumerate(alloc.remaining_agents()): + for i, student in enumerate(alloc.remaining_agents()): map_courses_to_student = alloc.remaining_items_for_agent(student) sorted_courses = sorted(map_courses_to_student, key=lambda course: alloc.effective_value(student, course)) - for i, course in enumerate(alloc.remaining_items()): + for j, course in enumerate(alloc.remaining_items()): if course in sorted_courses: - rank_mat[i][j] = sorted_courses.index(course) + 1 + rank_mat[j][i] = sorted_courses.index(course) + 1 - objective_Z1 = cp.Maximize(cp.sum([rank_mat[i][j] * x[i, j] - for i, course in enumerate(alloc.remaining_items()) - for j, student in enumerate(alloc.remaining_agents()) + logger.info("Rank matrix: %s", rank_mat) + + objective_Z1 = cp.Maximize(cp.sum([rank_mat[j][i] * x[j, i] + for j, course in enumerate(alloc.remaining_items()) + for i, student in enumerate(alloc.remaining_agents()) if (student, course) not in alloc.remaining_conflicts])) constraints_Z1 = [] # condition number 2: - # check that the number of students who took course j does not exceed the capacity of the course - for i, course in enumerate(alloc.remaining_items()): - constraints_Z1.append(cp.sum(x[i, :]) <= alloc.remaining_item_capacities[course]) - - # if the course have conflict - for j, student in enumerate(alloc.remaining_agents()): - if (student, course) in alloc.remaining_conflicts: - constraints_Z1.append(x[i, j] == 0) + notExceedtheCapacity(constraints_Z1, x, alloc) # condition number 3: # Each student can take at most k courses - for j, student in enumerate(alloc.remaining_agents()): - constraints_Z1.append(cp.sum(x[:, j]) <= alloc.remaining_agent_capacities[student]) + numberOfCourses(constraints_Z1, x, alloc, alloc.remaining_agent_capacities) + problem = cp.Problem(objective_Z1, constraints=constraints_Z1) result_Z1 = problem.solve() + logger.info("result_Z1 - the optimum ranking: %d", result_Z1) x = cvxpy.Variable((len(alloc.remaining_items()), len(alloc.remaining_agents())), boolean=True) # Is there a func which zero all the matrix? - objective_Z2 = cp.Maximize(cp.sum([alloc.effective_value(student, course) * x[i, j] - for i, course in enumerate(alloc.remaining_items()) - for j, student in enumerate(alloc.remaining_agents()) + objective_Z2 = cp.Maximize(cp.sum([alloc.effective_value(student, course) * x[j, i] + for j, course in enumerate(alloc.remaining_items()) + for i, student in enumerate(alloc.remaining_agents()) if (student, course) not in alloc.remaining_conflicts])) # condition number 19: constraints_Z2 = [] - constraints_Z2.append(cp.sum([rank_mat[i][j] * x[i, j] - for i, course in enumerate(alloc.remaining_items()) - for j, student in enumerate(alloc.remaining_agents()) + constraints_Z2.append(cp.sum([rank_mat[j][i] * x[j, i] + for j, course in enumerate(alloc.remaining_items()) + for i, student in enumerate(alloc.remaining_agents()) if (student, course) not in alloc.remaining_conflicts ]) == result_Z1) # condition number 2: - # check that the number of students who took course j does not exceed the capacity of the course - for i, course in enumerate(alloc.remaining_items()): - constraints_Z2.append(cp.sum(x[i, :]) <= alloc.remaining_item_capacities[course]) - - # if the course have conflict - for j, student in enumerate(alloc.remaining_agents()): - if (student, course) in alloc.remaining_conflicts: - constraints_Z2.append(x[i, j] == 0) + notExceedtheCapacity(constraints_Z2, x, alloc) # condition number 3: # Each student can take at most k courses - for j, student in enumerate(alloc.remaining_agents()): - constraints_Z2.append(cp.sum(x[:, j]) <= alloc.remaining_agent_capacities[student]) + numberOfCourses(constraints_Z2, x, alloc, alloc.remaining_agent_capacities) problem = cp.Problem(objective_Z2, constraints=constraints_Z2) result_Z2 = problem.solve() + logger.info("result_Z2 - the optimum bids: %d", result_Z2) # Check if the optimization problem was successfully solved if result_Z2 is not None: - # Extract the optimized values of x - x_values = x.value - - # Initialize a dictionary where each student will have an empty list - assign_map_courses_to_student = {student: [] for student in alloc.remaining_agents()} - - # Iterate over students and courses to populate the lists - for j, student in enumerate(alloc.remaining_agents()): - for i, course in enumerate(alloc.remaining_items()): - if x_values[i, j] == 1: - assign_map_courses_to_student[student].append(course) - - # Assign the courses to students based on the dictionary - for student, courses in assign_map_courses_to_student.items(): - for course in courses: - alloc.give(student, course) + alloctions(alloc, x, result_Z2, logger) optimal_value = problem.value explanation_logger.info("Optimal Objective Value:", optimal_value) @@ -134,3 +146,16 @@ def OC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger import doctest, sys print(doctest.testmod()) + logger.addHandler(logging.StreamHandler()) + logger.setLevel(logging.INFO) + + from fairpyx.adaptors import divide + s1 = {"c1": 27, "c2": 26, "c3": 24, "c4": 23} # c2 c3 c4 -> 6, 73 c1 c2 c3 -> 9, 77 + s2 = {"c1": 21, "c2": 20, "c3": 40, "c4": 19} # c3 c1 c2 -> 9, 81 c3 c2 c4 -> 6, 79 + instance = Instance( + agent_capacities={"s1": 3, "s2": 3}, + item_capacities={"c1": 1, "c2": 2, "c3": 2, "c4": 1}, + valuations={"s1": s1, "s2": s2} + ) + divide(OC_function, instance=instance) + diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py index 7515a16..72971b0 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py @@ -10,6 +10,40 @@ import logging logger = logging.getLogger(__name__) +# check that the number of students who took course j does not exceed the capacity of the course +def notExceedtheCapacity(constraints, var, alloc): + for j, course in enumerate(alloc.remaining_items()): + constraints.append(cp.sum(var[j, :]) <= alloc.remaining_item_capacities[course]) + + for i, student in enumerate(alloc.remaining_agents()): + if (student, course) in alloc.remaining_conflicts: + constraints.append(var[j, i] == 0) + + +# check that each student receives only one course in each iteration +def numberOfCourses(constraints, var, alloc, less_then): + for i, student in enumerate(alloc.remaining_agents()): + constraints.append(cp.sum(var[:, i]) <= less_then) + +def alloctions(alloc, var, logger): + # Extract the optimized values of x + x_values = var.value + logger.info("x_values - the optimum allocation: %s", x_values) + + # Initialize a dictionary where each student will have an empty list + assign_map_courses_to_student = {student: [] for student in alloc.remaining_agents()} + + # Iterate over students and courses to populate the lists + for i, student in enumerate(alloc.remaining_agents()): + for j, course in enumerate(alloc.remaining_items()): + if x_values[j, i] == 1: + assign_map_courses_to_student[student].append(course) + + # Assign the courses to students based on the dictionary + for student, courses in assign_map_courses_to_student.items(): + for course in courses: + alloc.give(student, course) + def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger = ExplanationLogger()): """ Algorethem 4: Allocate the given items to the given agents using the SP-O protocol. @@ -21,14 +55,14 @@ def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogge the fair course allocation problem(CAP). - >>> from fairpyx.adaptors import divide - >>> s1 = {"c1": 50, "c2": 49, "c3": 1} - >>> s2 = {"c1": 48, "c2": 46, "c3": 6} - >>> agent_capacities = {"s1": 1, "s2": 1} # 2 seats required - >>> course_capacities = {"c1": 1, "c2": 1, "c3": 1} # 3 seats available - >>> valuations = {"s1": s1, "s2": s2} - >>> instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) - >>> divide(SP_O_function, instance=instance) + #>>> from fairpyx.adaptors import divide + #>>> s1 = {"c1": 50, "c2": 49, "c3": 1} + #>>> s2 = {"c1": 48, "c2": 46, "c3": 6} + #>>> agent_capacities = {"s1": 1, "s2": 1} # 2 seats required + #>>> course_capacities = {"c1": 1, "c2": 1, "c3": 1} # 3 seats available + #>>> valuations = {"s1": s1, "s2": s2} + #>>> instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) + #>>> divide(SP_O_function, instance=instance) {'s1': ['c2'], 's2': ['c1']} """ @@ -36,46 +70,47 @@ def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogge max_iterations = max(alloc.remaining_agent_capacities[agent] for agent in alloc.remaining_agents()) # the amount of courses of student with maximum needed courses + logger.info("Max iterations: %d", max_iterations) for iteration in range(max_iterations): + logger.info("Iteration number: %d", iteration+1) + if len(alloc.remaining_agent_capacities) == 0 or len(alloc.remaining_item_capacities) == 0: # check if all the agents got their courses or there are no more + logger.info("There are no more agents (%d) or items(%d) ", len(alloc.remaining_agent_capacities),len(alloc.remaining_item_capacities)) + break rank_mat = [[0 for _ in range(len(alloc.remaining_agents()))] for _ in range(len(alloc.remaining_items()))] - for j, student in enumerate(alloc.remaining_agents()): + for i, student in enumerate(alloc.remaining_agents()): map_courses_to_student = alloc.remaining_items_for_agent(student) - sorted_courses = sorted(map_courses_to_student, key=lambda course: alloc.effective_value(student, course), ) + sorted_courses = sorted(map_courses_to_student, key=lambda course: alloc.effective_value(student, course)) - for i, course in enumerate(alloc.remaining_items()): + for j, course in enumerate(alloc.remaining_items()): if course in sorted_courses: - rank_mat[i][j] = sorted_courses.index(course) + 1 + rank_mat[j][i] = sorted_courses.index(course) + 1 else: - rank_mat[i][j] = len(sorted_courses) + 1 + rank_mat[j][i] = len(sorted_courses) + 1 + + logger.info("Rank matrix: %s", rank_mat) x = cvxpy.Variable((len(alloc.remaining_items()), len(alloc.remaining_agents())), boolean=True) - objective_Zt1 = cp.Maximize(cp.sum([rank_mat[i][j] * x[i, j] - for i, course in enumerate(alloc.remaining_items()) - for j, student in enumerate(alloc.remaining_agents()) + objective_Zt1 = cp.Maximize(cp.sum([rank_mat[j][i] * x[j, i] + for j, course in enumerate(alloc.remaining_items()) + for i, student in enumerate(alloc.remaining_agents()) if (student, course) not in alloc.remaining_conflicts])) constraints_Zt1 = [] # condition number 7: - # check that the number of students who took course j does not exceed the capacity of the course - for i, course in enumerate(alloc.remaining_items()): - constraints_Zt1.append(cp.sum(x[i, :]) <= alloc.remaining_item_capacities[course]) - - for j, student in enumerate(alloc.remaining_agents()): - if (student, course) in alloc.remaining_conflicts: - constraints_Zt1.append(x[i, j] == 0) + notExceedtheCapacity(constraints_Zt1, x, alloc) # condition number 8: - # check that each student receives only one course in each iteration - for j, student in enumerate(alloc.remaining_agents()): - constraints_Zt1.append(cp.sum(x[:, j]) <= 1) + numberOfCourses(constraints_Zt1, x, alloc, 1) problem = cp.Problem(objective_Zt1, constraints=constraints_Zt1) result_Zt1 = problem.solve() # This is the optimal value of program (6)(7)(8)(9). + logger.info("result_Zt1 - the optimum ranking: %d", result_Zt1) + # condition number 12: D = cvxpy.Variable() p = cvxpy.Variable(len(alloc.remaining_items())) @@ -115,20 +150,11 @@ def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogge problem = cp.Problem(objective_Wt2, constraints=constraints_Wt2) result_Wt2 = problem.solve() # This is the optimal price + logger.info("result_Wt2 - the optimum price: %d", result_Wt2) + # Check if the optimization problem was successfully solved if result_Zt1 is not None: - # Extract the optimized values of x - x_values = x.value - - # Assign courses based on the optimized values of x - assign_map_course_to_student = {} - for j, student in enumerate(alloc.remaining_agents()): - for i, course in enumerate(alloc.remaining_items()): - if x_values[i, j] == 1: - assign_map_course_to_student[student] = course - - for student, course in assign_map_course_to_student.items(): - alloc.give(student, course) + alloctions(alloc, x, logger) optimal_value = problem.value explanation_logger.info("Optimal Objective Value:", optimal_value) @@ -138,5 +164,19 @@ def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogge if __name__ == "__main__": - import doctest, sys - print(doctest.testmod()) \ No newline at end of file + #import doctest, sys + #print(doctest.testmod()) + + logger.addHandler(logging.StreamHandler()) + logger.setLevel(logging.INFO) + + from fairpyx.adaptors import divide + s1 = {"c1": 40, "c2": 10, "c3": 20, "c4": 30} + s2 = {"c1": 50, "c2": 10, "c3": 15, "c4": 25} + s3 = {"c1": 60, "c2": 30, "c3": 2, "c4": 8} + instance = Instance( + agent_capacities={"s1": 2, "s2": 2, "s3": 2}, + item_capacities={"c1": 1, "c2": 2, "c3": 2, "c4": 1}, + valuations={"s1": s1, "s2": s2, "s3": s3} + ) + divide(SP_O_function, instance=instance) \ No newline at end of file diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py index be999f4..6140492 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py @@ -5,28 +5,48 @@ Programmer: Tamar Bar-Ilan, Moriya Ester Ohayon, Ofek Kats """ import cvxpy +import numpy as np +import fairpyx +#import conditions from fairpyx import Instance, AllocationBuilder, ExplanationLogger import logging import cvxpy as cp logger = logging.getLogger(__name__) - # check that the number of students who took course j does not exceed the capacity of the course -def conditionNumber7(constraints, var, alloc): - for i, course in enumerate(alloc.remaining_items()): - constraints.append(cp.sum(var[i, :]) <= alloc.remaining_item_capacities[course]) +def notExceedtheCapacity(constraints, var, alloc): + for j, course in enumerate(alloc.remaining_items()): + constraints.append(cp.sum(var[j, :]) <= alloc.remaining_item_capacities[course]) - for j, student in enumerate(alloc.remaining_agents()): + for i, student in enumerate(alloc.remaining_agents()): if (student, course) in alloc.remaining_conflicts: - constraints.append(var[i, j] == 0) + constraints.append(var[j, i] == 0) # check that each student receives only one course in each iteration -def conditionNumber8(constraints, var, alloc): - for j, student in enumerate(alloc.remaining_agents()): - constraints.append(cp.sum(var[:, j]) <= 1) +def numberOfCourses(constraints, var, alloc, less_then): + for i, student in enumerate(alloc.remaining_agents()): + constraints.append(cp.sum(var[:, i]) <= less_then) + +def alloctions(alloc, var, logger): + # Extract the optimized values of x + x_values = var.value + logger.info("x_values - the optimum allocation: %s", x_values) + + # Initialize a dictionary where each student will have an empty list + assign_map_courses_to_student = {student: [] for student in alloc.remaining_agents()} + # Iterate over students and courses to populate the lists + for i, student in enumerate(alloc.remaining_agents()): + for j, course in enumerate(alloc.remaining_items()): + if x_values[j, i] == 1: + assign_map_courses_to_student[student].append(course) + + # Assign the courses to students based on the dictionary + for student, courses in assign_map_courses_to_student.items(): + for course in courses: + alloc.give(student, course) def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger = ExplanationLogger()): """ @@ -39,14 +59,14 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg the fair course allocation problem(CAP). - #>>> from fairpyx.adaptors import divide - #>>> s1 = {"c1": 50, "c2": 49, "c3": 1} - #>>> s2 = {"c1": 48, "c2": 46, "c3": 6} - #>>> agent_capacities = {"s1": 1, "s2": 1} # 2 seats required - #>>> course_capacities = {"c1": 1, "c2": 1, "c3": 1} # 3 seats available - #>>> valuations = {"s1": s1, "s2": s2} - #>>> instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) - #>>> divide(TTC_O_function, instance=instance) + >>> from fairpyx.adaptors import divide + >>> s1 = {"c1": 50, "c2": 49, "c3": 1} + >>> s2 = {"c1": 48, "c2": 46, "c3": 6} + >>> agent_capacities = {"s1": 1, "s2": 1} # 2 seats required + >>> course_capacities = {"c1": 1, "c2": 1, "c3": 1} # 3 seats available + >>> valuations = {"s1": s1, "s2": s2} + >>> instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) + >>> divide(TTC_O_function, instance=instance) {'s1': ['c2'], 's2': ['c1']} """ explanation_logger.info("\nAlgorithm TTC-O starts.\n") @@ -55,33 +75,36 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg logger.info("Max iterations: %d", max_iterations) for iteration in range(max_iterations): logger.info("Iteration number: %d", iteration+1) + if len(alloc.remaining_agent_capacities) == 0 or len(alloc.remaining_item_capacities) == 0: # check if all the agents got their courses or there are no more + logger.info("There are no more agents (%d) or items(%d) ", len(alloc.remaining_agent_capacities),len(alloc.remaining_item_capacities)) + break rank_mat = [[0 for _ in range(len(alloc.remaining_agents()))] for _ in range(len(alloc.remaining_items()))] - for j, student in enumerate(alloc.remaining_agents()): + for i, student in enumerate(alloc.remaining_agents()): map_courses_to_student = alloc.remaining_items_for_agent(student) sorted_courses = sorted(map_courses_to_student, key=lambda course: alloc.effective_value(student, course), ) - for i, course in enumerate(alloc.remaining_items()): + for j, course in enumerate(alloc.remaining_items()): if course in sorted_courses: - rank_mat[i][j] = sorted_courses.index(course) + 1 + rank_mat[j][i] = sorted_courses.index(course) + 1 logger.info("Rank matrix: %s", rank_mat) x = cvxpy.Variable((len(alloc.remaining_items()), len(alloc.remaining_agents())), boolean=True) - objective_Zt1 = cp.Maximize(cp.sum([rank_mat[i][j] * x[i,j] - for i, course in enumerate(alloc.remaining_items()) - for j, student in enumerate(alloc.remaining_agents()) + objective_Zt1 = cp.Maximize(cp.sum([rank_mat[j][i] * x[j,i] + for j, course in enumerate(alloc.remaining_items()) + for i, student in enumerate(alloc.remaining_agents()) if (student, course) not in alloc.remaining_conflicts])) constraints_Zt1 = [] # condition number 7: - conditionNumber7(constraints_Zt1, x, alloc) + notExceedtheCapacity(constraints_Zt1, x, alloc) # condition number 8: - conditionNumber8(constraints_Zt1, x, alloc) + numberOfCourses(constraints_Zt1, x, alloc, 1) problem = cp.Problem(objective_Zt1, constraints=constraints_Zt1) result_Zt1 = problem.solve() # This is the optimal value of program (6)(7)(8)(9). @@ -91,22 +114,22 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg # Write and solve new program for Zt2 (10)(11)(7)(8) x = cvxpy.Variable((len(alloc.remaining_items()), len(alloc.remaining_agents())), boolean=True) #Is there a func which zero all the matrix? - objective_Zt2 = cp.Maximize(cp.sum([alloc.effective_value(student, course) * x[i, j] - for i, course in enumerate(alloc.remaining_items()) - for j, student in enumerate(alloc.remaining_agents()) + objective_Zt2 = cp.Maximize(cp.sum([alloc.effective_value(student, course) * x[j, i] + for j, course in enumerate(alloc.remaining_items()) + for i, student in enumerate(alloc.remaining_agents()) if (student, course) not in alloc.remaining_conflicts])) constraints_Zt2 = [] # condition number 7: - conditionNumber7(constraints_Zt2, x, alloc) + notExceedtheCapacity(constraints_Zt2, x, alloc) # condition number 8: - conditionNumber8(constraints_Zt2, x, alloc) + numberOfCourses(constraints_Zt2, x, alloc, 1) - constraints_Zt2.append(cp.sum([rank_mat[i][j] * x[i, j] - for i, course in enumerate(alloc.remaining_items()) - for j, student in enumerate(alloc.remaining_agents()) + constraints_Zt2.append(cp.sum([rank_mat[j][i] * x[j, i] + for j, course in enumerate(alloc.remaining_items()) + for i, student in enumerate(alloc.remaining_agents()) if (student, course) not in alloc.remaining_conflicts ]) == result_Zt1) @@ -117,19 +140,7 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg # Check if the optimization problem was successfully solved if result_Zt2 is not None: - # Extract the optimized values of x - x_values = x.value - logger.info("x_values - the optimum allocation: %s", x_values) - - # Assign courses based on the optimized values of x - assign_map_course_to_student = {} - for j, student in enumerate(alloc.remaining_agents()): - for i, course in enumerate(alloc.remaining_items()): - if x_values[i, j] == 1: - assign_map_course_to_student[student] = course - - for student, course in assign_map_course_to_student.items(): - alloc.give(student,course) + alloctions(alloc, x, logger) optimal_value = problem.value explanation_logger.info("Optimal Objective Value:", optimal_value) @@ -139,20 +150,22 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg if __name__ == "__main__": - #import doctest - #print(doctest.testmod()) + import doctest + print(doctest.testmod()) logger.addHandler(logging.StreamHandler()) logger.setLevel(logging.INFO) from fairpyx.adaptors import divide - s1 = {"c1": 50, "c2": 10, "c3": 40} # rank: {"c1": 3, "c2": 1, "c3": 2} his rank:11 our:13 - s2 = {"c1": 45, "c2": 30, "c3": 25} # rank: {"c1": 3, "c2": 2, "c3": 1} his bids:184 our:210 - s3 = {"c1": 49, "c2": 15, "c3": 36} # rank: {"c1": 3, "c2": 1, "c3": 2} - instance = Instance( - agent_capacities={"s1": 2, "s2": 2, "s3": 2}, - item_capacities={"c1": 2, "c2": 2, "c3": 2}, - valuations={"s1": s1, "s2": s2, "s3": s3} + np.random.seed(2) + instance = fairpyx.Instance.random_uniform( + num_of_agents=70, num_of_items=10, normalized_sum_of_values=100, + agent_capacity_bounds=[2, 6], + item_capacity_bounds=[20, 40], + item_base_value_bounds=[1, 1000], + item_subjective_ratio_bounds=[0.5, 1.5] ) + allocation = divide(TTC_O_function, instance=instance) + fairpyx.validate_allocation(instance, allocation, title=f"Seed {5}, TTC_O_function") divide(TTC_O_function, instance=instance) \ No newline at end of file diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/__init__.py b/fairpyx/algorithms/Optimization_based_Mechanisms/__init__.py index 5dc463b..06614cf 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/__init__.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/__init__.py @@ -3,4 +3,5 @@ from fairpyx.algorithms.Optimization_based_Mechanisms.SP import SP_function from fairpyx.algorithms.Optimization_based_Mechanisms.TTC_O import TTC_O_function from fairpyx.algorithms.Optimization_based_Mechanisms.TTC import TTC_function +from fairpyx.algorithms.Optimization_based_Mechanisms.optimal_functions import numberOfCourses, numberOfCourses, alloctions diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/optimal_functions.py b/fairpyx/algorithms/Optimization_based_Mechanisms/optimal_functions.py new file mode 100644 index 0000000..ce932c5 --- /dev/null +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/optimal_functions.py @@ -0,0 +1,40 @@ +import cvxpy as cp + +# check that the number of students who took course j does not exceed the capacity of the course +def notExceedtheCapacity(constraints, var, alloc): + for j, course in enumerate(alloc.remaining_items()): + constraints.append(cp.sum(var[j, :]) <= alloc.remaining_item_capacities[course]) + + for i, student in enumerate(alloc.remaining_agents()): + if (student, course) in alloc.remaining_conflicts: + constraints.append(var[j, i] == 0) + + +# check that each student receives only one course in each iteration +def numberOfCourses(constraints, var, alloc, less_then): + for i, student in enumerate(alloc.remaining_agents()): + if less_then == 1: + constraints.append(cp.sum(var[:, i]) <= less_then) + else: + constraints.append(cp.sum(var[:, i]) <= less_then[student]) + + +def alloctions(alloc, var, logger): + # Extract the optimized values of x + x_values = var.value + logger.info("x_values - the optimum allocation: %s", x_values) + + # Initialize a dictionary where each student will have an empty list + assign_map_courses_to_student = {student: [] for student in alloc.remaining_agents()} + + # Iterate over students and courses to populate the lists + for i, student in enumerate(alloc.remaining_agents()): + for j, course in enumerate(alloc.remaining_items()): + if x_values[j, i] == 1: + assign_map_courses_to_student[student].append(course) + + # Assign the courses to students based on the dictionary + for student, courses in assign_map_courses_to_student.items(): + for course in courses: + alloc.give(student, course) + diff --git a/fairpyx/algorithms/__init__.py b/fairpyx/algorithms/__init__.py index cf404b0..a84b78d 100644 --- a/fairpyx/algorithms/__init__.py +++ b/fairpyx/algorithms/__init__.py @@ -6,4 +6,5 @@ from fairpyx.algorithms.Optimization_based_Mechanisms.SP_O import SP_O_function from fairpyx.algorithms.Optimization_based_Mechanisms.SP import SP_function from fairpyx.algorithms.Optimization_based_Mechanisms.TTC_O import TTC_O_function -from fairpyx.algorithms.Optimization_based_Mechanisms.TTC import TTC_function \ No newline at end of file +from fairpyx.algorithms.Optimization_based_Mechanisms.TTC import TTC_function +from fairpyx.algorithms.Optimization_based_Mechanisms.optimal_functions import notExceedtheCapacity, numberOfCourses, alloctions diff --git a/tests/Tests_for_Optimization-based_Mechanisms/test_TTC_O.py b/tests/Tests_for_Optimization-based_Mechanisms/test_TTC_O.py index 15b9494..cb70bba 100644 --- a/tests/Tests_for_Optimization-based_Mechanisms/test_TTC_O.py +++ b/tests/Tests_for_Optimization-based_Mechanisms/test_TTC_O.py @@ -82,7 +82,7 @@ def test_random(): for i in range(NUM_OF_RANDOM_INSTANCES): np.random.seed(i) instance = fairpyx.Instance.random_uniform( - num_of_agents=70, num_of_items=10, normalized_sum_of_values=1000, + num_of_agents=70, num_of_items=10, normalized_sum_of_values=100, agent_capacity_bounds=[2, 6], item_capacity_bounds=[20, 40], item_base_value_bounds=[1, 1000], From ee3570157b4189709ff44cb083c4873be3c2c49a Mon Sep 17 00:00:00 2001 From: ofekats Date: Tue, 4 Jun 2024 18:52:38 +0300 Subject: [PATCH 24/42] OC with give of its own --- .../Optimization_based_Mechanisms/OC.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py b/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py index 2537ceb..c65b1de 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py @@ -133,7 +133,22 @@ def OC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger # Check if the optimization problem was successfully solved if result_Z2 is not None: - alloctions(alloc, x, result_Z2, logger) + # Extract the optimized values of x + x_values = x.value + + # Initialize a dictionary where each student will have an empty list + assign_map_courses_to_student = {student: [] for student in alloc.remaining_agents()} + + # Iterate over students and courses to populate the lists + for j, student in enumerate(alloc.remaining_agents()): + for i, course in enumerate(alloc.remaining_items()): + if x_values[i, j] == 1: + assign_map_courses_to_student[student].append(course) + + # Assign the courses to students based on the dictionary + for student, courses in assign_map_courses_to_student.items(): + for course in courses: + alloc.give(student, course) optimal_value = problem.value explanation_logger.info("Optimal Objective Value:", optimal_value) From b591a5201054e20658a650a886ee248d5989ca80 Mon Sep 17 00:00:00 2001 From: MoriyaEster <120458205+MoriyaEster@users.noreply.github.com> Date: Tue, 4 Jun 2024 19:04:54 +0300 Subject: [PATCH 25/42] Update OC.py --- .../Optimization_based_Mechanisms/OC.py | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py b/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py index c65b1de..e463b15 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py @@ -133,22 +133,7 @@ def OC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger # Check if the optimization problem was successfully solved if result_Z2 is not None: - # Extract the optimized values of x - x_values = x.value - - # Initialize a dictionary where each student will have an empty list - assign_map_courses_to_student = {student: [] for student in alloc.remaining_agents()} - - # Iterate over students and courses to populate the lists - for j, student in enumerate(alloc.remaining_agents()): - for i, course in enumerate(alloc.remaining_items()): - if x_values[i, j] == 1: - assign_map_courses_to_student[student].append(course) - - # Assign the courses to students based on the dictionary - for student, courses in assign_map_courses_to_student.items(): - for course in courses: - alloc.give(student, course) + alloctions(alloc, var, logger) optimal_value = problem.value explanation_logger.info("Optimal Objective Value:", optimal_value) From cb3f1635d16140b4ff31c2d0f1148630c8011cd1 Mon Sep 17 00:00:00 2001 From: MoriyaEster Date: Tue, 4 Jun 2024 19:06:04 +0300 Subject: [PATCH 26/42] logger for all and outer functions --- fairpyx/algorithms/Optimization_based_Mechanisms/OC.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py b/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py index e463b15..c00ba43 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py @@ -133,7 +133,7 @@ def OC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger # Check if the optimization problem was successfully solved if result_Z2 is not None: - alloctions(alloc, var, logger) + alloctions(alloc, x, logger) optimal_value = problem.value explanation_logger.info("Optimal Objective Value:", optimal_value) From 4b85078036d2572735424b1e30dd95e30add7af5 Mon Sep 17 00:00:00 2001 From: MoriyaEster Date: Tue, 4 Jun 2024 19:09:21 +0300 Subject: [PATCH 27/42] logger for all and outer functions --- .../Optimization_based_Mechanisms/OC.py | 36 +++++++++---------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py b/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py index c00ba43..21f1fcd 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py @@ -30,25 +30,6 @@ def numberOfCourses(constraints, var, alloc, less_then): else: constraints.append(cp.sum(var[:, i]) <= less_then[student]) -def alloctions(alloc, var, logger): - # Extract the optimized values of x - x_values = var.value - logger.info("x_values - the optimum allocation: %s", x_values) - - # Initialize a dictionary where each student will have an empty list - assign_map_courses_to_student = {student: [] for student in alloc.remaining_agents()} - - # Iterate over students and courses to populate the lists - for i, student in enumerate(alloc.remaining_agents()): - for j, course in enumerate(alloc.remaining_items()): - if x_values[j, i] == 1: - assign_map_courses_to_student[student].append(course) - - # Assign the courses to students based on the dictionary - for student, courses in assign_map_courses_to_student.items(): - for course in courses: - alloc.give(student, course) - def OC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger = ExplanationLogger()): """ @@ -133,7 +114,22 @@ def OC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger # Check if the optimization problem was successfully solved if result_Z2 is not None: - alloctions(alloc, x, logger) + # Extract the optimized values of x + x_values = x.value + + # Initialize a dictionary where each student will have an empty list + assign_map_courses_to_student = {student: [] for student in alloc.remaining_agents()} + + # Iterate over students and courses to populate the lists + for j, student in enumerate(alloc.remaining_agents()): + for i, course in enumerate(alloc.remaining_items()): + if x_values[i, j] == 1: + assign_map_courses_to_student[student].append(course) + + # Assign the courses to students based on the dictionary + for student, courses in assign_map_courses_to_student.items(): + for course in courses: + alloc.give(student, course) optimal_value = problem.value explanation_logger.info("Optimal Objective Value:", optimal_value) From 3164117ab88e3d33fc2d5ba065154f361f532fd0 Mon Sep 17 00:00:00 2001 From: TamarBarIlan Date: Tue, 4 Jun 2024 19:29:06 +0300 Subject: [PATCH 28/42] adding test running in test_OC --- .../test_OC.py | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/tests/Tests_for_Optimization-based_Mechanisms/test_OC.py b/tests/Tests_for_Optimization-based_Mechanisms/test_OC.py index 76d568f..845cde1 100644 --- a/tests/Tests_for_Optimization-based_Mechanisms/test_OC.py +++ b/tests/Tests_for_Optimization-based_Mechanisms/test_OC.py @@ -13,8 +13,8 @@ NUM_OF_RANDOM_INSTANCES=10 def test_optimal_number_of_courses(): - s1 = {"c1": 27, "c2": 26, "c3": 24, "c4": 23} #c2 c3 c4 -> 6, 73 c1 c2 c3 -> 9, 77 - s2 = {"c1": 21, "c2": 20, "c3": 40, "c4": 19} #c3 c1 c2 -> 9, 81 c3 c2 c4 -> 6, 79 + s1 = {"c1": 27, "c2": 26, "c3": 24, "c4": 23} + s2 = {"c1": 21, "c2": 20, "c3": 40, "c4": 19} instance = fairpyx.Instance( agent_capacities={"s1": 3, "s2": 3}, item_capacities={"c1": 1, "c2": 2, "c3": 2, "c4": 1}, @@ -45,6 +45,32 @@ def test_student_bids_the_same_for_different_courses(): assert fairpyx.divide(fairpyx.algorithms.OC_function, instance=instance) == {'s1': ['c1', 'c3'], 's2': ['c1', 'c2']}, "ERROR" +def test_same_order_of_course(): + s1 = {"c1": 40, "c2": 30, "c3": 20, "c4": 10} + s2 = {"c1": 70, "c2": 20, "c3": 6, "c4": 4} + s3 = {"c1": 50, "c2": 21, "c3": 20, "c4": 9} + s4 = {"c1": 55, "c2": 25, "c3": 15, "c4": 5} + s5 = {"c1": 90, "c2": 5, "c3": 3, "c4": 2} + instance = fairpyx.Instance( + agent_capacities={"s1": 2, "s2": 2, "s3": 2, "s4": 2, "s5": 2}, + item_capacities={"c1": 3, "c2": 2, "c3": 2, "c4": 2}, + valuations={"s1": s1, "s2": s2, "s3": s3, "s4": s4, "s5": s5} + ) + assert fairpyx.divide(fairpyx.algorithms.OC_function, instance=instance) == {'s1': ['c2', 'c3'], 's2': ['c1', 'c4'], 's3': ['c3', 'c4'], 's4': ['c1', 'c2'], 's5': ['c1']}, "ERROR" + +def test_big_example(): + s1 = {"c1": 40, "c2": 20, "c3": 10, "c4": 30} + s2 = {"c1": 6, "c2": 20, "c3": 70, "c4": 4} + s3 = {"c1": 9, "c2": 20, "c3": 21, "c4": 50} + s4 = {"c1": 25, "c2": 5, "c3": 15, "c4": 55} + s5 = {"c1": 5, "c2": 90, "c3": 3, "c4": 2} + instance = fairpyx.Instance( + agent_capacities={"s1": 2, "s2": 2, "s3": 2, "s4": 2, "s5": 2}, + item_capacities={"c1": 3, "c2": 2, "c3": 2, "c4": 2}, + valuations={"s1": s1, "s2": s2, "s3": s3, "s4": s4, "s5": s5} + ) + assert fairpyx.divide(fairpyx.algorithms.OC_function, instance=instance) == {'s1': ['c1'], 's2': ['c2', 'c3'], 's3': ['c3', 'c4'], 's4': ['c1', 'c4'], 's5': ['c1', 'c2']}, "ERROR" + def test_random(): for i in range(NUM_OF_RANDOM_INSTANCES): np.random.seed(i) @@ -58,5 +84,7 @@ def test_random(): allocation = fairpyx.divide(fairpyx.algorithms.OC_function, instance=instance) fairpyx.validate_allocation(instance, allocation, title=f"Seed {i}, OC_function") + + if __name__ == "__main__": pytest.main(["-v",__file__]) From f1055847aced4caf10805f369e8362905efd6642 Mon Sep 17 00:00:00 2001 From: MoriyaEster Date: Thu, 6 Jun 2024 21:04:58 +0300 Subject: [PATCH 29/42] changed according Erel comments --- .../Optimization_based_Mechanisms/OC.py | 143 ++++++------------ .../Optimization_based_Mechanisms/SP_O.py | 69 +-------- .../Optimization_based_Mechanisms/TTC_O.py | 115 ++------------ .../optimal_functions.py | 72 ++++++++- .../test_OC.py | 2 +- 5 files changed, 134 insertions(+), 267 deletions(-) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py b/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py index 21f1fcd..883d277 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py @@ -10,26 +10,9 @@ from fairpyx import Instance, AllocationBuilder, ExplanationLogger import logging import cvxpy as cp +import fairpyx.algorithms.Optimization_based_Mechanisms.optimal_functions as optimal logger = logging.getLogger(__name__) -# check that the number of students who took course j does not exceed the capacity of the course -def notExceedtheCapacity(constraints, var, alloc): - for j, course in enumerate(alloc.remaining_items()): - constraints.append(cp.sum(var[j, :]) <= alloc.remaining_item_capacities[course]) - - for i, student in enumerate(alloc.remaining_agents()): - if (student, course) in alloc.remaining_conflicts: - constraints.append(var[j, i] == 0) - - -# check that each student receives only one course in each iteration -def numberOfCourses(constraints, var, alloc, less_then): - for i, student in enumerate(alloc.remaining_agents()): - if less_then == 1: - constraints.append(cp.sum(var[:, i]) <= less_then) - else: - constraints.append(cp.sum(var[:, i]) <= less_then[student]) - def OC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger = ExplanationLogger()): """ @@ -41,14 +24,14 @@ def OC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger :param alloc: an allocation builder, which tracks the allocation and the remaining capacity for items and agents of the fair course allocation problem(CAP). - >>> from fairpyx.adaptors import divide - >>> s1 = {"c1": 44, "c2": 39, "c3": 17} - >>> s2 = {"c1": 50, "c2": 45, "c3": 5} - >>> agent_capacities = {"s1": 2, "s2": 2} # 4 seats required - >>> course_capacities = {"c1": 2, "c2": 1, "c3": 2} # 5 seats available - >>> valuations = {"s1": s1, "s2": s2} - >>> instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) - >>> divide(OC_function, instance=instance) + #>>> from fairpyx.adaptors import divide + #>>> s1 = {"c1": 44, "c2": 39, "c3": 17} + #>>> s2 = {"c1": 50, "c2": 45, "c3": 5} + #>>> agent_capacities = {"s1": 2, "s2": 2} # 4 seats required + #>>> course_capacities = {"c1": 2, "c2": 1, "c3": 2} # 5 seats available + #>>> valuations = {"s1": s1, "s2": s2} + #>>> instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) + #>>> divide(OC_function, instance=instance) {'s1': ['c1', 'c3'], 's2': ['c1', 'c2']} """ @@ -56,31 +39,13 @@ def OC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger x = cvxpy.Variable((len(alloc.remaining_items()), len(alloc.remaining_agents())), boolean=True) - rank_mat = [[0 for _ in range(len(alloc.remaining_agents()))] for _ in range(len(alloc.remaining_items()))] + rank_mat = optimal.createRankMat(alloc,logger) - for i, student in enumerate(alloc.remaining_agents()): - map_courses_to_student = alloc.remaining_items_for_agent(student) - sorted_courses = sorted(map_courses_to_student, key=lambda course: alloc.effective_value(student, course)) + sum_rank = optimal.sumOnRankMat(alloc, rank_mat, x) - for j, course in enumerate(alloc.remaining_items()): - if course in sorted_courses: - rank_mat[j][i] = sorted_courses.index(course) + 1 - - logger.info("Rank matrix: %s", rank_mat) - - objective_Z1 = cp.Maximize(cp.sum([rank_mat[j][i] * x[j, i] - for j, course in enumerate(alloc.remaining_items()) - for i, student in enumerate(alloc.remaining_agents()) - if (student, course) not in alloc.remaining_conflicts])) - - constraints_Z1 = [] - # condition number 2: - notExceedtheCapacity(constraints_Z1, x, alloc) - - # condition number 3: - # Each student can take at most k courses - numberOfCourses(constraints_Z1, x, alloc, alloc.remaining_agent_capacities) + objective_Z1 = cp.Maximize(sum_rank) + constraints_Z1 = optimal.notExceedtheCapacity(x,alloc) + optimal.numberOfCourses(x, alloc, alloc.remaining_agent_capacities) problem = cp.Problem(objective_Z1, constraints=constraints_Z1) result_Z1 = problem.solve() @@ -94,64 +59,48 @@ def OC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger if (student, course) not in alloc.remaining_conflicts])) # condition number 19: - constraints_Z2 = [] - constraints_Z2.append(cp.sum([rank_mat[j][i] * x[j, i] - for j, course in enumerate(alloc.remaining_items()) - for i, student in enumerate(alloc.remaining_agents()) - if (student, course) not in alloc.remaining_conflicts - ]) == result_Z1) - - # condition number 2: - notExceedtheCapacity(constraints_Z2, x, alloc) - - # condition number 3: - # Each student can take at most k courses - numberOfCourses(constraints_Z2, x, alloc, alloc.remaining_agent_capacities) - - problem = cp.Problem(objective_Z2, constraints=constraints_Z2) - result_Z2 = problem.solve() - logger.info("result_Z2 - the optimum bids: %d", result_Z2) - - # Check if the optimization problem was successfully solved - if result_Z2 is not None: - # Extract the optimized values of x - x_values = x.value - - # Initialize a dictionary where each student will have an empty list - assign_map_courses_to_student = {student: [] for student in alloc.remaining_agents()} - - # Iterate over students and courses to populate the lists - for j, student in enumerate(alloc.remaining_agents()): - for i, course in enumerate(alloc.remaining_items()): - if x_values[i, j] == 1: - assign_map_courses_to_student[student].append(course) - - # Assign the courses to students based on the dictionary - for student, courses in assign_map_courses_to_student.items(): - for course in courses: - alloc.give(student, course) - - optimal_value = problem.value - explanation_logger.info("Optimal Objective Value:", optimal_value) - # Now you can use this optimal value for further processing - else: - explanation_logger.info("Solver failed to find a solution or the problem is infeasible/unbounded.") + constraints_Z2 = optimal.notExceedtheCapacity(x, alloc) + optimal.numberOfCourses(x, alloc, alloc.remaining_agent_capacities) + + constraints_Z2.append(sum_rank == result_Z1) + + try: + problem = cp.Problem(objective_Z2, constraints=constraints_Z2) + result_Z2 = problem.solve() + logger.info("result_Z2 - the optimum bids: %d", result_Z2) + + # Check if the optimization problem was successfully solved + if result_Z2 is not None: + optimal.alloctions(alloc, x, logger) + + optimal_value = problem.value + explanation_logger.info("Optimal Objective Value:", optimal_value) + # Now you can use this optimal value for further processing + else: + explanation_logger.info("Solver failed to find a solution or the problem is infeasible/unbounded.") + raise ValueError("Solver failed to find a solution or the problem is infeasible/unbounded.") + except Exception as e: + explanation_logger.info("Solver failed: %s", str(e)) + logger.error("An error occurred: %s", str(e)) + raise if __name__ == "__main__": - import doctest, sys - print(doctest.testmod()) + #import doctest, sys + #print(doctest.testmod()) logger.addHandler(logging.StreamHandler()) logger.setLevel(logging.INFO) from fairpyx.adaptors import divide - s1 = {"c1": 27, "c2": 26, "c3": 24, "c4": 23} # c2 c3 c4 -> 6, 73 c1 c2 c3 -> 9, 77 - s2 = {"c1": 21, "c2": 20, "c3": 40, "c4": 19} # c3 c1 c2 -> 9, 81 c3 c2 c4 -> 6, 79 + s1 = {"c1": 40, "c2": 20, "c3": 10, "c4": 30} + s2 = {"c1": 6, "c2": 20, "c3": 70, "c4": 4} + s3 = {"c1": 9, "c2": 20, "c3": 21, "c4": 50} + s4 = {"c1": 25, "c2": 5, "c3": 15, "c4": 55} + s5 = {"c1": 5, "c2": 90, "c3": 3, "c4": 2} instance = Instance( - agent_capacities={"s1": 3, "s2": 3}, - item_capacities={"c1": 1, "c2": 2, "c3": 2, "c4": 1}, - valuations={"s1": s1, "s2": s2} + agent_capacities={"s1": 2, "s2": 2, "s3": 2, "s4": 2, "s5": 2}, + item_capacities={"c1": 3, "c2": 2, "c3": 2, "c4": 2}, + valuations={"s1": s1, "s2": s2, "s3": s3, "s4": s4, "s5": s5} ) divide(OC_function, instance=instance) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py index 72971b0..87535e6 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py @@ -8,42 +8,9 @@ from fairpyx import Instance, AllocationBuilder, ExplanationLogger import cvxpy as cp import logging +import fairpyx.algorithms.Optimization_based_Mechanisms.optimal_functions as optimal logger = logging.getLogger(__name__) -# check that the number of students who took course j does not exceed the capacity of the course -def notExceedtheCapacity(constraints, var, alloc): - for j, course in enumerate(alloc.remaining_items()): - constraints.append(cp.sum(var[j, :]) <= alloc.remaining_item_capacities[course]) - - for i, student in enumerate(alloc.remaining_agents()): - if (student, course) in alloc.remaining_conflicts: - constraints.append(var[j, i] == 0) - - -# check that each student receives only one course in each iteration -def numberOfCourses(constraints, var, alloc, less_then): - for i, student in enumerate(alloc.remaining_agents()): - constraints.append(cp.sum(var[:, i]) <= less_then) - -def alloctions(alloc, var, logger): - # Extract the optimized values of x - x_values = var.value - logger.info("x_values - the optimum allocation: %s", x_values) - - # Initialize a dictionary where each student will have an empty list - assign_map_courses_to_student = {student: [] for student in alloc.remaining_agents()} - - # Iterate over students and courses to populate the lists - for i, student in enumerate(alloc.remaining_agents()): - for j, course in enumerate(alloc.remaining_items()): - if x_values[j, i] == 1: - assign_map_courses_to_student[student].append(course) - - # Assign the courses to students based on the dictionary - for student, courses in assign_map_courses_to_student.items(): - for course in courses: - alloc.give(student, course) - def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger = ExplanationLogger()): """ Algorethem 4: Allocate the given items to the given agents using the SP-O protocol. @@ -71,42 +38,16 @@ def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogge max_iterations = max(alloc.remaining_agent_capacities[agent] for agent in alloc.remaining_agents()) # the amount of courses of student with maximum needed courses logger.info("Max iterations: %d", max_iterations) + + # the amount of bids agent have from all the courses he got before + map_student_to_his_sum_bids = {s: 0 for s in alloc.remaining_agents()} + for iteration in range(max_iterations): logger.info("Iteration number: %d", iteration+1) if len(alloc.remaining_agent_capacities) == 0 or len(alloc.remaining_item_capacities) == 0: # check if all the agents got their courses or there are no more logger.info("There are no more agents (%d) or items(%d) ", len(alloc.remaining_agent_capacities),len(alloc.remaining_item_capacities)) break - rank_mat = [[0 for _ in range(len(alloc.remaining_agents()))] for _ in range(len(alloc.remaining_items()))] - - for i, student in enumerate(alloc.remaining_agents()): - map_courses_to_student = alloc.remaining_items_for_agent(student) - sorted_courses = sorted(map_courses_to_student, key=lambda course: alloc.effective_value(student, course)) - - for j, course in enumerate(alloc.remaining_items()): - if course in sorted_courses: - rank_mat[j][i] = sorted_courses.index(course) + 1 - else: - rank_mat[j][i] = len(sorted_courses) + 1 - - logger.info("Rank matrix: %s", rank_mat) - - x = cvxpy.Variable((len(alloc.remaining_items()), len(alloc.remaining_agents())), boolean=True) - - objective_Zt1 = cp.Maximize(cp.sum([rank_mat[j][i] * x[j, i] - for j, course in enumerate(alloc.remaining_items()) - for i, student in enumerate(alloc.remaining_agents()) - if (student, course) not in alloc.remaining_conflicts])) - - constraints_Zt1 = [] - - # condition number 7: - notExceedtheCapacity(constraints_Zt1, x, alloc) - - # condition number 8: - numberOfCourses(constraints_Zt1, x, alloc, 1) - - problem = cp.Problem(objective_Zt1, constraints=constraints_Zt1) result_Zt1 = problem.solve() # This is the optimal value of program (6)(7)(8)(9). logger.info("result_Zt1 - the optimum ranking: %d", result_Zt1) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py index 6140492..12ad45a 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py @@ -7,46 +7,14 @@ import cvxpy import numpy as np import fairpyx -#import conditions +import fairpyx.algorithms.Optimization_based_Mechanisms.optimal_functions as optimal from fairpyx import Instance, AllocationBuilder, ExplanationLogger import logging import cvxpy as cp logger = logging.getLogger(__name__) -# check that the number of students who took course j does not exceed the capacity of the course -def notExceedtheCapacity(constraints, var, alloc): - for j, course in enumerate(alloc.remaining_items()): - constraints.append(cp.sum(var[j, :]) <= alloc.remaining_item_capacities[course]) - for i, student in enumerate(alloc.remaining_agents()): - if (student, course) in alloc.remaining_conflicts: - constraints.append(var[j, i] == 0) - - -# check that each student receives only one course in each iteration -def numberOfCourses(constraints, var, alloc, less_then): - for i, student in enumerate(alloc.remaining_agents()): - constraints.append(cp.sum(var[:, i]) <= less_then) - -def alloctions(alloc, var, logger): - # Extract the optimized values of x - x_values = var.value - logger.info("x_values - the optimum allocation: %s", x_values) - - # Initialize a dictionary where each student will have an empty list - assign_map_courses_to_student = {student: [] for student in alloc.remaining_agents()} - - # Iterate over students and courses to populate the lists - for i, student in enumerate(alloc.remaining_agents()): - for j, course in enumerate(alloc.remaining_items()): - if x_values[j, i] == 1: - assign_map_courses_to_student[student].append(course) - - # Assign the courses to students based on the dictionary - for student, courses in assign_map_courses_to_student.items(): - for course in courses: - alloc.give(student, course) def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger = ExplanationLogger()): """ @@ -59,14 +27,14 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg the fair course allocation problem(CAP). - >>> from fairpyx.adaptors import divide - >>> s1 = {"c1": 50, "c2": 49, "c3": 1} - >>> s2 = {"c1": 48, "c2": 46, "c3": 6} - >>> agent_capacities = {"s1": 1, "s2": 1} # 2 seats required - >>> course_capacities = {"c1": 1, "c2": 1, "c3": 1} # 3 seats available - >>> valuations = {"s1": s1, "s2": s2} - >>> instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) - >>> divide(TTC_O_function, instance=instance) + #>>> from fairpyx.adaptors import divide + #>>> s1 = {"c1": 50, "c2": 49, "c3": 1} + #>>> s2 = {"c1": 48, "c2": 46, "c3": 6} + #>>> agent_capacities = {"s1": 1, "s2": 1} # 2 seats required + #>>> course_capacities = {"c1": 1, "c2": 1, "c3": 1} # 3 seats available + #>>> valuations = {"s1": s1, "s2": s2} + #>>> instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) + #>>> divide(TTC_O_function, instance=instance) {'s1': ['c2'], 's2': ['c1']} """ explanation_logger.info("\nAlgorithm TTC-O starts.\n") @@ -79,68 +47,11 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg logger.info("There are no more agents (%d) or items(%d) ", len(alloc.remaining_agent_capacities),len(alloc.remaining_item_capacities)) break - rank_mat = [[0 for _ in range(len(alloc.remaining_agents()))] for _ in range(len(alloc.remaining_items()))] - - for i, student in enumerate(alloc.remaining_agents()): - map_courses_to_student = alloc.remaining_items_for_agent(student) - sorted_courses = sorted(map_courses_to_student, key=lambda course: alloc.effective_value(student, course), ) - - for j, course in enumerate(alloc.remaining_items()): - if course in sorted_courses: - rank_mat[j][i] = sorted_courses.index(course) + 1 - - logger.info("Rank matrix: %s", rank_mat) - - x = cvxpy.Variable((len(alloc.remaining_items()), len(alloc.remaining_agents())), boolean=True) - - objective_Zt1 = cp.Maximize(cp.sum([rank_mat[j][i] * x[j,i] - for j, course in enumerate(alloc.remaining_items()) - for i, student in enumerate(alloc.remaining_agents()) - if (student, course) not in alloc.remaining_conflicts])) - - constraints_Zt1 = [] - - # condition number 7: - notExceedtheCapacity(constraints_Zt1, x, alloc) - - # condition number 8: - numberOfCourses(constraints_Zt1, x, alloc, 1) - - problem = cp.Problem(objective_Zt1, constraints=constraints_Zt1) - result_Zt1 = problem.solve() # This is the optimal value of program (6)(7)(8)(9). - logger.info("result_Zt1 - the optimum ranking: %d", result_Zt1) - - - # Write and solve new program for Zt2 (10)(11)(7)(8) - x = cvxpy.Variable((len(alloc.remaining_items()), len(alloc.remaining_agents())), boolean=True) #Is there a func which zero all the matrix? - - objective_Zt2 = cp.Maximize(cp.sum([alloc.effective_value(student, course) * x[j, i] - for j, course in enumerate(alloc.remaining_items()) - for i, student in enumerate(alloc.remaining_agents()) - if (student, course) not in alloc.remaining_conflicts])) - - constraints_Zt2 = [] - - # condition number 7: - notExceedtheCapacity(constraints_Zt2, x, alloc) - - # condition number 8: - numberOfCourses(constraints_Zt2, x, alloc, 1) - - constraints_Zt2.append(cp.sum([rank_mat[j][i] * x[j, i] - for j, course in enumerate(alloc.remaining_items()) - for i, student in enumerate(alloc.remaining_agents()) - if (student, course) not in alloc.remaining_conflicts - ]) == result_Zt1) - - problem = cp.Problem(objective_Zt2, constraints=constraints_Zt2) - result_Zt2 = problem.solve() - logger.info("result_Zt2 - the optimum bids: %d", result_Zt2) - + result_Zt2, var, problem = optimal.roundTTC_O(alloc, logger, alloc.effective_value) # Check if the optimization problem was successfully solved if result_Zt2 is not None: - alloctions(alloc, x, logger) + optimal.alloctions(alloc, var, logger) optimal_value = problem.value explanation_logger.info("Optimal Objective Value:", optimal_value) @@ -150,8 +61,8 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg if __name__ == "__main__": - import doctest - print(doctest.testmod()) + #import doctest + #print(doctest.testmod()) logger.addHandler(logging.StreamHandler()) logger.setLevel(logging.INFO) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/optimal_functions.py b/fairpyx/algorithms/Optimization_based_Mechanisms/optimal_functions.py index ce932c5..56a9a8f 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/optimal_functions.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/optimal_functions.py @@ -1,22 +1,28 @@ +import cvxpy import cvxpy as cp # check that the number of students who took course j does not exceed the capacity of the course -def notExceedtheCapacity(constraints, var, alloc): +def notExceedtheCapacity(var, alloc): + constraints = [] for j, course in enumerate(alloc.remaining_items()): constraints.append(cp.sum(var[j, :]) <= alloc.remaining_item_capacities[course]) for i, student in enumerate(alloc.remaining_agents()): if (student, course) in alloc.remaining_conflicts: constraints.append(var[j, i] == 0) + return constraints # check that each student receives only one course in each iteration -def numberOfCourses(constraints, var, alloc, less_then): +def numberOfCourses(var, alloc, less_then): + constraints = [] for i, student in enumerate(alloc.remaining_agents()): if less_then == 1: constraints.append(cp.sum(var[:, i]) <= less_then) else: constraints.append(cp.sum(var[:, i]) <= less_then[student]) + return constraints + def alloctions(alloc, var, logger): @@ -36,5 +42,65 @@ def alloctions(alloc, var, logger): # Assign the courses to students based on the dictionary for student, courses in assign_map_courses_to_student.items(): for course in courses: - alloc.give(student, course) + alloc.give(student, course, logger) + + +def createRankMat(alloc, logger): + rank_mat = [[0 for _ in range(len(alloc.remaining_agents()))] for _ in range(len(alloc.remaining_items()))] + + for i, student in enumerate(alloc.remaining_agents()): + map_courses_to_student = alloc.remaining_items_for_agent(student) + sorted_courses = sorted(map_courses_to_student, key=lambda course: alloc.effective_value(student, course), ) + + for j, course in enumerate(alloc.remaining_items()): + if course in sorted_courses: + rank_mat[j][i] = sorted_courses.index(course) + 1 + + logger.info("Rank matrix: %s", rank_mat) + return rank_mat + +def sumOnRankMat(alloc, rank_mat, var): + return cp.sum([rank_mat[j][i] * var[j, i] + for j, course in enumerate(alloc.remaining_items()) + for i, student in enumerate(alloc.remaining_agents()) + if (student, course) not in alloc.remaining_conflicts]) + +def roundTTC_O(alloc, logger, func): + rank_mat = createRankMat(alloc, logger) + + x = cvxpy.Variable((len(alloc.remaining_items()), len(alloc.remaining_agents())), boolean=True) + + sum_rank = sumOnRankMat(alloc, rank_mat, x) + + objective_Zt1 = cp.Maximize(sum_rank) + + constraints_Zt1 = notExceedtheCapacity(x, alloc) + numberOfCourses(x, alloc, 1) + + problem = cp.Problem(objective_Zt1, constraints=constraints_Zt1) + result_Zt1 = problem.solve() # This is the optimal value of program (6)(7)(8)(9). + logger.info("result_Zt1 - the optimum ranking: %d", result_Zt1) + + # Write and solve new program for Zt2 (10)(11)(7)(8) + x = cvxpy.Variable((len(alloc.remaining_items()), len(alloc.remaining_agents())), + boolean=True) # Is there a func which zero all the matrix? + + objective_Zt2 = cp.Maximize(cp.sum([func(student, course) * x[j, i] + for j, course in enumerate(alloc.remaining_items()) + for i, student in enumerate(alloc.remaining_agents()) + if (student, course) not in alloc.remaining_conflicts])) + + constraints_Zt2 = notExceedtheCapacity(x, alloc) + numberOfCourses(x, alloc, 1) + + constraints_Zt2.append(sum_rank == result_Zt1) + + try: + problem = cp.Problem(objective_Zt2, constraints=constraints_Zt2) + result_Zt2 = problem.solve() + logger.info("result_Zt2 - the optimum bids: %d", result_Zt2) + + except Exception as e: + explanation_logger.info("Solver failed: %s", str(e)) + logger.error("An error occurred: %s", str(e)) + raise + return result_Zt2, x, problem diff --git a/tests/Tests_for_Optimization-based_Mechanisms/test_OC.py b/tests/Tests_for_Optimization-based_Mechanisms/test_OC.py index 845cde1..037f236 100644 --- a/tests/Tests_for_Optimization-based_Mechanisms/test_OC.py +++ b/tests/Tests_for_Optimization-based_Mechanisms/test_OC.py @@ -69,7 +69,7 @@ def test_big_example(): item_capacities={"c1": 3, "c2": 2, "c3": 2, "c4": 2}, valuations={"s1": s1, "s2": s2, "s3": s3, "s4": s4, "s5": s5} ) - assert fairpyx.divide(fairpyx.algorithms.OC_function, instance=instance) == {'s1': ['c1'], 's2': ['c2', 'c3'], 's3': ['c3', 'c4'], 's4': ['c1', 'c4'], 's5': ['c1', 'c2']}, "ERROR" + assert fairpyx.divide(fairpyx.algorithms.OC_function, instance=instance) == {'s1': ['c1','c2'], 's2': ['c1', 'c3'], 's3': ['c3', 'c4'], 's4': ['c1', 'c4'], 's5': ['c2']}, "ERROR" def test_random(): for i in range(NUM_OF_RANDOM_INSTANCES): From ea4630ef54f1bff90f12a32a99b50f4b696492d5 Mon Sep 17 00:00:00 2001 From: ofekats Date: Sun, 9 Jun 2024 16:07:11 +0300 Subject: [PATCH 30/42] SP-O completed --- .../Optimization_based_Mechanisms/SP_O.py | 72 ++++++++++++------- .../Optimization_based_Mechanisms/TTC_O.py | 2 +- .../optimal_functions.py | 29 ++++++-- .../test_SP_O.py | 4 +- 4 files changed, 70 insertions(+), 37 deletions(-) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py index 87535e6..421ce65 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py @@ -11,9 +11,18 @@ import fairpyx.algorithms.Optimization_based_Mechanisms.optimal_functions as optimal logger = logging.getLogger(__name__) +# global dict +map_student_to_his_sum_bids = {} + + +def effective_value_with_price(alloc, student, course): + global map_student_to_his_sum_bids + return alloc.effective_value(student, course) + map_student_to_his_sum_bids[student] + + def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger = ExplanationLogger()): """ - Algorethem 4: Allocate the given items to the given agents using the SP-O protocol. + Algorithm 4: Allocate the given items to the given agents using the SP-O protocol. SP-O in each round distributes one course to each student, with the refund of the bids according to the price of the course. Uses linear planning for optimality. @@ -34,6 +43,7 @@ def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogge """ explanation_logger.info("\nAlgorithm SP-O starts.\n") + global map_student_to_his_sum_bids max_iterations = max(alloc.remaining_agent_capacities[agent] for agent in alloc.remaining_agents()) # the amount of courses of student with maximum needed courses @@ -48,45 +58,43 @@ def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogge logger.info("There are no more agents (%d) or items(%d) ", len(alloc.remaining_agent_capacities),len(alloc.remaining_item_capacities)) break - result_Zt1 = problem.solve() # This is the optimal value of program (6)(7)(8)(9). + logger.info("map_student_to_his_sum_bids : " + str(map_student_to_his_sum_bids)) + result_Zt1, result_Zt2, x, problem, rank_mat = optimal.roundTTC_O(alloc, logger, effective_value_with_price, 1) # This is the TTC-O round. - logger.info("result_Zt1 - the optimum ranking: %d", result_Zt1) + optimal_value = problem.value + logger.info("Optimal Objective Value: %d", optimal_value) + # SP-O # condition number 12: D = cvxpy.Variable() - p = cvxpy.Variable(len(alloc.remaining_items())) - v = cvxpy.Variable(len(alloc.remaining_agents())) - objective_Wt1 = cp.Minimize(result_Zt1*D - + cp.sum(alloc.remaining_item_capacities[course]*p[j] for j, course in enumerate(alloc.remaining_items())) - + cp.sum(v[i] for i, student in enumerate(alloc.remaining_agents()))) + p = cvxpy.Variable(len(alloc.remaining_items())) # list of price to each course + v = cvxpy.Variable(len(alloc.remaining_agents())) # list of value to each student + + linear_problem = optimal.SP_O_condition(alloc, result_Zt1, D, p, v) + # linear problem number 12: + objective_Wt1 = cp.Minimize(linear_problem) constraints_Wt1 = [] # condition number 13 + 14: for j, course in enumerate(alloc.remaining_items()): for i, student in enumerate(alloc.remaining_agents()): - constraints_Wt1.append(p[j]+v[i]+rank_mat[i][j]*D >= alloc.effective_value(student,course)) - constraints_Wt1.append(p[j] >= 0) - constraints_Wt1.append(v[i] >= 0) + constraints_Wt1.append(p[j]+v[i]+rank_mat[j][i]*D >= alloc.effective_value(student,course)) + + condition_14 = optimal.conditions_14(alloc, v, p) + constraints_Wt1 += condition_14 problem = cp.Problem(objective_Wt1, constraints=constraints_Wt1) result_Wt1 = problem.solve() # This is the optimal value of program (12)(13)(14). - objective_Wt2 = cp.Minimize(cp.sum(alloc.remaining_item_capacities[course]* p[j] - for j, course in enumerate(alloc.remaining_items()))) + # linear problem number 15: + objective_Wt2 = cp.Minimize(cp.sum([alloc.remaining_item_capacities[course] * p[j] + for j, course in enumerate(alloc.remaining_items())])) constraints_Wt2 = [] - - constraints_Wt2.append(result_Zt1*D - + cp.sum(alloc.remaining_item_capacities[course]* p[j] - for j, course in enumerate(alloc.remaining_items())) - +cp.sum(v[i] for i, student in enumerate(alloc.remaining_agents())) == result_Wt1) - - for j in range(len(alloc.remaining_items())): - for i in range(len(alloc.remaining_agents())): - constraints_Wt2.append(p[j] >= 0) - constraints_Wt2.append(v[i] >= 0) + constraints_Wt2.append(linear_problem == result_Wt1) + constraints_Wt2 += condition_14 problem = cp.Problem(objective_Wt2, constraints=constraints_Wt2) result_Wt2 = problem.solve() # This is the optimal price @@ -94,14 +102,24 @@ def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogge logger.info("result_Wt2 - the optimum price: %d", result_Wt2) # Check if the optimization problem was successfully solved - if result_Zt1 is not None: - alloctions(alloc, x, logger) + if result_Wt2 is not None: + x_values = x.value + p_values = p.value + logger.info("p values: " + str(p_values)) + # Iterate over students and courses to pay the price of the course each student got + for i, student in enumerate(alloc.remaining_agents()): + for j, course in enumerate(alloc.remaining_items()): + if x_values[j, i] == 1: + map_student_to_his_sum_bids[student] -= p_values[j] + logger.info("student %s payed: %f", student, p_values[j]) + logger.info("student %s have in dict: %f", student, map_student_to_his_sum_bids[student]) + optimal.alloctions(alloc, x, logger) optimal_value = problem.value - explanation_logger.info("Optimal Objective Value:", optimal_value) + logger.info("Optimal Objective Value: %d", optimal_value) # Now you can use this optimal value for further processing else: - explanation_logger.info("Solver failed to find a solution or the problem is infeasible/unbounded.") + logger.info("Solver failed to find a solution or the problem is infeasible/unbounded.") if __name__ == "__main__": diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py index 12ad45a..315d420 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py @@ -47,7 +47,7 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg logger.info("There are no more agents (%d) or items(%d) ", len(alloc.remaining_agent_capacities),len(alloc.remaining_item_capacities)) break - result_Zt2, var, problem = optimal.roundTTC_O(alloc, logger, alloc.effective_value) + result_Zt1, result_Zt2, var, problem , rank_mat= optimal.roundTTC_O(alloc, logger, alloc.effective_value, 0) # Check if the optimization problem was successfully solved if result_Zt2 is not None: diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/optimal_functions.py b/fairpyx/algorithms/Optimization_based_Mechanisms/optimal_functions.py index 56a9a8f..ae53f44 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/optimal_functions.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/optimal_functions.py @@ -59,13 +59,26 @@ def createRankMat(alloc, logger): logger.info("Rank matrix: %s", rank_mat) return rank_mat +def SP_O_condition(alloc, result_Zt1, D, p, v): + return result_Zt1 * D + cp.sum([alloc.remaining_item_capacities[course] * p[j] for j, course in enumerate(alloc.remaining_items())]) + cp.sum([v[i] for i, student in enumerate(alloc.remaining_agents())]) + +def conditions_14(alloc, v,p): + constraints = [] + for j, course in enumerate(alloc.remaining_items()): + for i, student in enumerate(alloc.remaining_agents()): + constraints.append(p[j] >= 0) + constraints.append(v[i] >= 0) + return constraints + def sumOnRankMat(alloc, rank_mat, var): return cp.sum([rank_mat[j][i] * var[j, i] for j, course in enumerate(alloc.remaining_items()) for i, student in enumerate(alloc.remaining_agents()) if (student, course) not in alloc.remaining_conflicts]) -def roundTTC_O(alloc, logger, func): +# if flag_if_use_alloc_in_func == 0 then using alloc.effective_value +# if flag_if_use_alloc_in_func == 1 then using effective_value_with_price +def roundTTC_O(alloc, logger, func, flag_if_use_alloc_in_func): rank_mat = createRankMat(alloc, logger) x = cvxpy.Variable((len(alloc.remaining_items()), len(alloc.remaining_agents())), boolean=True) @@ -84,10 +97,12 @@ def roundTTC_O(alloc, logger, func): x = cvxpy.Variable((len(alloc.remaining_items()), len(alloc.remaining_agents())), boolean=True) # Is there a func which zero all the matrix? - objective_Zt2 = cp.Maximize(cp.sum([func(student, course) * x[j, i] - for j, course in enumerate(alloc.remaining_items()) - for i, student in enumerate(alloc.remaining_agents()) - if (student, course) not in alloc.remaining_conflicts])) + objective_Zt2 = cp.Maximize(cp.sum( + [func(student, course) * x[j, i] if flag_if_use_alloc_in_func == 0 else func(alloc, student, course) * x[j, i] + for j, course in enumerate(alloc.remaining_items()) + for i, student in enumerate(alloc.remaining_agents()) + if (student, course) not in alloc.remaining_conflicts] + )) constraints_Zt2 = notExceedtheCapacity(x, alloc) + numberOfCourses(x, alloc, 1) @@ -99,8 +114,8 @@ def roundTTC_O(alloc, logger, func): logger.info("result_Zt2 - the optimum bids: %d", result_Zt2) except Exception as e: - explanation_logger.info("Solver failed: %s", str(e)) + logger.info("Solver failed: %s", str(e)) logger.error("An error occurred: %s", str(e)) raise - return result_Zt2, x, problem + return result_Zt1, result_Zt2, x, problem, rank_mat diff --git a/tests/Tests_for_Optimization-based_Mechanisms/test_SP_O.py b/tests/Tests_for_Optimization-based_Mechanisms/test_SP_O.py index f1dd603..b7da60b 100644 --- a/tests/Tests_for_Optimization-based_Mechanisms/test_SP_O.py +++ b/tests/Tests_for_Optimization-based_Mechanisms/test_SP_O.py @@ -35,7 +35,7 @@ def test_optimal_improve_cardinal_and_ordinal_results(): valuations={"s1": s1, "s2": s2, "s3": s3} ) - assert fairpyx.divide(fairpyx.algorithms.SP_O_function, instance=instance) == {'s1': ['c1', 'c2'], 's2': ['c2', 'c3'], 's3': ['c1', 'c2']}, "ERROR" + assert fairpyx.divide(fairpyx.algorithms.SP_O_function, instance=instance) == {'s1': ['c1', 'c2'], 's2': ['c2'], 's3': ['c1', 'c3']}, "ERROR" def test_sub_round_within_sub_round(): @@ -48,7 +48,7 @@ def test_sub_round_within_sub_round(): valuations={"s1": s1, "s2": s2, "s3": s3} ) - assert fairpyx.divide(fairpyx.algorithms.SP_O_function, instance=instance) == {'s1': ['c3', 'c4'], 's2': ['c1', 'c2'], 's3': ['c2', 'c3']}, "ERROR" + assert fairpyx.divide(fairpyx.algorithms.SP_O_function, instance=instance) == {'s1': ['c3', 'c4'], 's2': ['c1', 'c3'], 's3': ['c2']}, "ERROR" def test_random(): From b1c85c2135505ff87a5201d3e12871567a9b34ad Mon Sep 17 00:00:00 2001 From: MoriyaEster Date: Mon, 10 Jun 2024 23:55:16 +0300 Subject: [PATCH 31/42] logger for all and outer functions --- .../Optimization_based_Mechanisms/OC.py | 36 +++++------------- .../Optimization_based_Mechanisms/SP.py | 27 +------------- .../Optimization_based_Mechanisms/SP_O.py | 35 +++++------------- .../Optimization_based_Mechanisms/TTC.py | 13 ------- .../Optimization_based_Mechanisms/TTC_O.py | 37 +++++-------------- 5 files changed, 32 insertions(+), 116 deletions(-) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py b/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py index 883d277..5576ef9 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py @@ -24,14 +24,14 @@ def OC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger :param alloc: an allocation builder, which tracks the allocation and the remaining capacity for items and agents of the fair course allocation problem(CAP). - #>>> from fairpyx.adaptors import divide - #>>> s1 = {"c1": 44, "c2": 39, "c3": 17} - #>>> s2 = {"c1": 50, "c2": 45, "c3": 5} - #>>> agent_capacities = {"s1": 2, "s2": 2} # 4 seats required - #>>> course_capacities = {"c1": 2, "c2": 1, "c3": 2} # 5 seats available - #>>> valuations = {"s1": s1, "s2": s2} - #>>> instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) - #>>> divide(OC_function, instance=instance) + >>> from fairpyx.adaptors import divide + >>> s1 = {"c1": 44, "c2": 39, "c3": 17} + >>> s2 = {"c1": 50, "c2": 45, "c3": 5} + >>> agent_capacities = {"s1": 2, "s2": 2} # 4 seats required + >>> course_capacities = {"c1": 2, "c2": 1, "c3": 2} # 5 seats available + >>> valuations = {"s1": s1, "s2": s2} + >>> instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) + >>> divide(OC_function, instance=instance) {'s1': ['c1', 'c3'], 's2': ['c1', 'c2']} """ @@ -85,22 +85,6 @@ def OC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger raise if __name__ == "__main__": - #import doctest, sys - #print(doctest.testmod()) - - logger.addHandler(logging.StreamHandler()) - logger.setLevel(logging.INFO) - - from fairpyx.adaptors import divide - s1 = {"c1": 40, "c2": 20, "c3": 10, "c4": 30} - s2 = {"c1": 6, "c2": 20, "c3": 70, "c4": 4} - s3 = {"c1": 9, "c2": 20, "c3": 21, "c4": 50} - s4 = {"c1": 25, "c2": 5, "c3": 15, "c4": 55} - s5 = {"c1": 5, "c2": 90, "c3": 3, "c4": 2} - instance = Instance( - agent_capacities={"s1": 2, "s2": 2, "s3": 2, "s4": 2, "s5": 2}, - item_capacities={"c1": 3, "c2": 2, "c3": 2, "c4": 2}, - valuations={"s1": s1, "s2": s2, "s3": s3, "s4": s4, "s5": s5} - ) - divide(OC_function, instance=instance) + import doctest + print(doctest.testmod()) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py b/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py index 6c44d76..7c8abbe 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py @@ -66,9 +66,7 @@ def SP_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger for course_that_no_longer_potential in map_student_to_course_with_no_seats_and_the_bids[current_agent]: # if the algorithm pass a course that the student can't get - add his bids if needed if alloc.effective_value(current_agent, current_course) < map_student_to_course_with_no_seats_and_the_bids[current_agent][course_that_no_longer_potential]: - #logger.info(f'map_student_to_his_sum_bids66{map_student_to_his_sum_bids}') map_student_to_his_sum_bids[current_agent] += map_student_to_course_with_no_seats_and_the_bids[current_agent][course_that_no_longer_potential] - #logger.info(f'map_student_to_his_sum_bids68{map_student_to_his_sum_bids}') pop_course_that_moved_bids.append(course_that_no_longer_potential) #save the courses should delete for course in pop_course_that_moved_bids: # remove the courses that the algorithm add the bids map_student_to_course_with_no_seats_and_the_bids[current_agent].pop(course, None) @@ -81,9 +79,7 @@ def SP_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger # 2. Allocate the remaining seats in each course: for student, course in map_agent_to_best_item.items(): # update the bids of each student - #logger.info(f'map_student_to_his_sum_bids81{map_student_to_his_sum_bids}') map_student_to_his_sum_bids[student] += alloc.effective_value(student, course) - #logger.info(f'map_student_to_his_sum_bids83{map_student_to_his_sum_bids}') # create dict for each course of student that point on the course and their bids map_course_to_students_with_max_bids = {course: @@ -114,8 +110,6 @@ def SP_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger if student in sorted_students_pointing_to_course[remaining_capacity:]: continue map_student_to_course_with_no_seats_and_the_bids[student][conflict_course] = alloc.effective_value(student, conflict_course) # save the bids for each student for the courses that have conflict - #logger.info(f'map_student_to_his_sum_bids{map_student_to_his_sum_bids}') - #logger.info("Student %s suggest %d bids for course %s", student,map_student_to_his_sum_bids[student], course) alloc.give(student, course, logger) agents_who_need_an_item_in_current_iteration.remove(student) # removing the agent from the set (dont worry he will come back in the next round) map_agent_to_best_item.pop(student, None) # Delete student if exists in the dict @@ -125,22 +119,5 @@ def SP_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger if __name__ == "__main__": - import doctest, sys - print(doctest.testmod()) - - #logger.addHandler(logging.StreamHandler()) - #logger.setLevel(logging.INFO) - - #from fairpyx.adaptors import divide - #for i in range(10): - # np.random.seed(i) - # instance = Instance.random_uniform( - # num_of_agents=10, num_of_items=5, normalized_sum_of_values=100, - # agent_capacity_bounds=[2,4], - # item_capacity_bounds=[2,5], - # item_base_value_bounds=[1,1000], - # item_subjective_ratio_bounds=[0.5, 1.5] - # ) - # allocation = divide(SP_function, instance=instance) - # fairpyx.validate_allocation(instance, allocation, title=f"Seed {i}, SP_function") - #divide(SP_function, instance=instance) \ No newline at end of file + import doctest + print(doctest.testmod()) \ No newline at end of file diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py index 421ce65..306a36f 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py @@ -14,7 +14,6 @@ # global dict map_student_to_his_sum_bids = {} - def effective_value_with_price(alloc, student, course): global map_student_to_his_sum_bids return alloc.effective_value(student, course) + map_student_to_his_sum_bids[student] @@ -31,14 +30,14 @@ def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogge the fair course allocation problem(CAP). - #>>> from fairpyx.adaptors import divide - #>>> s1 = {"c1": 50, "c2": 49, "c3": 1} - #>>> s2 = {"c1": 48, "c2": 46, "c3": 6} - #>>> agent_capacities = {"s1": 1, "s2": 1} # 2 seats required - #>>> course_capacities = {"c1": 1, "c2": 1, "c3": 1} # 3 seats available - #>>> valuations = {"s1": s1, "s2": s2} - #>>> instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) - #>>> divide(SP_O_function, instance=instance) + >>> from fairpyx.adaptors import divide + >>> s1 = {"c1": 50, "c2": 49, "c3": 1} + >>> s2 = {"c1": 48, "c2": 46, "c3": 6} + >>> agent_capacities = {"s1": 1, "s2": 1} # 2 seats required + >>> course_capacities = {"c1": 1, "c2": 1, "c3": 1} # 3 seats available + >>> valuations = {"s1": s1, "s2": s2} + >>> instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) + >>> divide(SP_O_function, instance=instance) {'s1': ['c2'], 's2': ['c1']} """ @@ -123,19 +122,5 @@ def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogge if __name__ == "__main__": - #import doctest, sys - #print(doctest.testmod()) - - logger.addHandler(logging.StreamHandler()) - logger.setLevel(logging.INFO) - - from fairpyx.adaptors import divide - s1 = {"c1": 40, "c2": 10, "c3": 20, "c4": 30} - s2 = {"c1": 50, "c2": 10, "c3": 15, "c4": 25} - s3 = {"c1": 60, "c2": 30, "c3": 2, "c4": 8} - instance = Instance( - agent_capacities={"s1": 2, "s2": 2, "s3": 2}, - item_capacities={"c1": 1, "c2": 2, "c3": 2, "c4": 1}, - valuations={"s1": s1, "s2": s2, "s3": s3} - ) - divide(SP_O_function, instance=instance) \ No newline at end of file + import doctest + print(doctest.testmod()) \ No newline at end of file diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py index 6bd0f89..eb65213 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py @@ -74,17 +74,4 @@ def TTC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger import doctest print("\n", doctest.testmod(), "\n") - #logger.addHandler(logging.StreamHandler()) - #logger.setLevel(logging.INFO) - - #from fairpyx.adaptors import divide - #s1 = {"c1": 50, "c2": 49, "c3": 1} - #s2 = {"c1": 48, "c2": 46, "c3": 6} - #agent_capacities = {"s1": 1, "s2": 1} # 2 seats required - #course_capacities = {"c1": 1, "c2": 1, "c3": 1} # 3 seats available - #valuations = {"s1": s1, "s2": s2} - #instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) - #divide(TTC_function, instance=instance) - - diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py index 315d420..81a4b55 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py @@ -27,14 +27,14 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg the fair course allocation problem(CAP). - #>>> from fairpyx.adaptors import divide - #>>> s1 = {"c1": 50, "c2": 49, "c3": 1} - #>>> s2 = {"c1": 48, "c2": 46, "c3": 6} - #>>> agent_capacities = {"s1": 1, "s2": 1} # 2 seats required - #>>> course_capacities = {"c1": 1, "c2": 1, "c3": 1} # 3 seats available - #>>> valuations = {"s1": s1, "s2": s2} - #>>> instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) - #>>> divide(TTC_O_function, instance=instance) + >>> from fairpyx.adaptors import divide + >>> s1 = {"c1": 50, "c2": 49, "c3": 1} + >>> s2 = {"c1": 48, "c2": 46, "c3": 6} + >>> agent_capacities = {"s1": 1, "s2": 1} # 2 seats required + >>> course_capacities = {"c1": 1, "c2": 1, "c3": 1} # 3 seats available + >>> valuations = {"s1": s1, "s2": s2} + >>> instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) + >>> divide(TTC_O_function, instance=instance) {'s1': ['c2'], 's2': ['c1']} """ explanation_logger.info("\nAlgorithm TTC-O starts.\n") @@ -61,22 +61,5 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg if __name__ == "__main__": - #import doctest - #print(doctest.testmod()) - - logger.addHandler(logging.StreamHandler()) - logger.setLevel(logging.INFO) - - from fairpyx.adaptors import divide - - np.random.seed(2) - instance = fairpyx.Instance.random_uniform( - num_of_agents=70, num_of_items=10, normalized_sum_of_values=100, - agent_capacity_bounds=[2, 6], - item_capacity_bounds=[20, 40], - item_base_value_bounds=[1, 1000], - item_subjective_ratio_bounds=[0.5, 1.5] - ) - allocation = divide(TTC_O_function, instance=instance) - fairpyx.validate_allocation(instance, allocation, title=f"Seed {5}, TTC_O_function") - divide(TTC_O_function, instance=instance) \ No newline at end of file + import doctest + print(doctest.testmod()) \ No newline at end of file From e02992d87ec337c8a30a30ba93b26e5f31c5c940 Mon Sep 17 00:00:00 2001 From: Erel Segal-Halevi Date: Tue, 11 Jun 2024 00:49:34 +0300 Subject: [PATCH 32/42] activate doctests --- .../Optimization_based_Mechanisms/OC.py | 21 ++++++++++--------- .../Optimization_based_Mechanisms/TTC_O.py | 21 ++++++++++--------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py b/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py index 883d277..19e0b4a 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py @@ -24,14 +24,14 @@ def OC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger :param alloc: an allocation builder, which tracks the allocation and the remaining capacity for items and agents of the fair course allocation problem(CAP). - #>>> from fairpyx.adaptors import divide - #>>> s1 = {"c1": 44, "c2": 39, "c3": 17} - #>>> s2 = {"c1": 50, "c2": 45, "c3": 5} - #>>> agent_capacities = {"s1": 2, "s2": 2} # 4 seats required - #>>> course_capacities = {"c1": 2, "c2": 1, "c3": 2} # 5 seats available - #>>> valuations = {"s1": s1, "s2": s2} - #>>> instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) - #>>> divide(OC_function, instance=instance) + >>> from fairpyx.adaptors import divide + >>> s1 = {"c1": 44, "c2": 39, "c3": 17} + >>> s2 = {"c1": 50, "c2": 45, "c3": 5} + >>> agent_capacities = {"s1": 2, "s2": 2} # 4 seats required + >>> course_capacities = {"c1": 2, "c2": 1, "c3": 2} # 5 seats available + >>> valuations = {"s1": s1, "s2": s2} + >>> instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) + >>> divide(OC_function, instance=instance) {'s1': ['c1', 'c3'], 's2': ['c1', 'c2']} """ @@ -85,8 +85,9 @@ def OC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger raise if __name__ == "__main__": - #import doctest, sys - #print(doctest.testmod()) + import doctest, sys + print("\n", doctest.testmod(), "\n") + sys.exit(1) logger.addHandler(logging.StreamHandler()) logger.setLevel(logging.INFO) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py index 315d420..998c3b1 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py @@ -27,14 +27,14 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg the fair course allocation problem(CAP). - #>>> from fairpyx.adaptors import divide - #>>> s1 = {"c1": 50, "c2": 49, "c3": 1} - #>>> s2 = {"c1": 48, "c2": 46, "c3": 6} - #>>> agent_capacities = {"s1": 1, "s2": 1} # 2 seats required - #>>> course_capacities = {"c1": 1, "c2": 1, "c3": 1} # 3 seats available - #>>> valuations = {"s1": s1, "s2": s2} - #>>> instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) - #>>> divide(TTC_O_function, instance=instance) + >>> from fairpyx.adaptors import divide + >>> s1 = {"c1": 50, "c2": 49, "c3": 1} + >>> s2 = {"c1": 48, "c2": 46, "c3": 6} + >>> agent_capacities = {"s1": 1, "s2": 1} # 2 seats required + >>> course_capacities = {"c1": 1, "c2": 1, "c3": 1} # 3 seats available + >>> valuations = {"s1": s1, "s2": s2} + >>> instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) + >>> divide(TTC_O_function, instance=instance) {'s1': ['c2'], 's2': ['c1']} """ explanation_logger.info("\nAlgorithm TTC-O starts.\n") @@ -61,8 +61,9 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg if __name__ == "__main__": - #import doctest - #print(doctest.testmod()) + import doctest, sys + print("\n", doctest.testmod(), "\n") + sys.exit(1) logger.addHandler(logging.StreamHandler()) logger.setLevel(logging.INFO) From c7e7402de504ef25442d9876279cfc1a9b2942b4 Mon Sep 17 00:00:00 2001 From: Erel Segal-Halevi Date: Tue, 11 Jun 2024 00:55:38 +0300 Subject: [PATCH 33/42] add main --- .../Optimization_based_Mechanisms/SP.py | 21 +++++++++++++++++-- .../Optimization_based_Mechanisms/SP_O.py | 21 +++++++++++++++++-- .../Optimization_based_Mechanisms/TTC.py | 18 +++++++++++++++- 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py b/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py index 7c8abbe..463f041 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py @@ -119,5 +119,22 @@ def SP_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger if __name__ == "__main__": - import doctest - print(doctest.testmod()) \ No newline at end of file + import doctest, sys + print("\n", doctest.testmod(), "\n") + sys.exit(1) + + logger.addHandler(logging.StreamHandler()) + logger.setLevel(logging.INFO) + + from fairpyx.adaptors import divide + s1 = {"c1": 40, "c2": 20, "c3": 10, "c4": 30} + s2 = {"c1": 6, "c2": 20, "c3": 70, "c4": 4} + s3 = {"c1": 9, "c2": 20, "c3": 21, "c4": 50} + s4 = {"c1": 25, "c2": 5, "c3": 15, "c4": 55} + s5 = {"c1": 5, "c2": 90, "c3": 3, "c4": 2} + instance = Instance( + agent_capacities={"s1": 2, "s2": 2, "s3": 2, "s4": 2, "s5": 2}, + item_capacities={"c1": 3, "c2": 2, "c3": 2, "c4": 2}, + valuations={"s1": s1, "s2": s2, "s3": s3, "s4": s4, "s5": s5} + ) + divide(SP_function, instance=instance) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py index 306a36f..4f4fb6a 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py @@ -122,5 +122,22 @@ def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogge if __name__ == "__main__": - import doctest - print(doctest.testmod()) \ No newline at end of file + import doctest, sys + print("\n", doctest.testmod(), "\n") + sys.exit(1) + + logger.addHandler(logging.StreamHandler()) + logger.setLevel(logging.INFO) + + from fairpyx.adaptors import divide + s1 = {"c1": 40, "c2": 20, "c3": 10, "c4": 30} + s2 = {"c1": 6, "c2": 20, "c3": 70, "c4": 4} + s3 = {"c1": 9, "c2": 20, "c3": 21, "c4": 50} + s4 = {"c1": 25, "c2": 5, "c3": 15, "c4": 55} + s5 = {"c1": 5, "c2": 90, "c3": 3, "c4": 2} + instance = Instance( + agent_capacities={"s1": 2, "s2": 2, "s3": 2, "s4": 2, "s5": 2}, + item_capacities={"c1": 3, "c2": 2, "c3": 2, "c4": 2}, + valuations={"s1": s1, "s2": s2, "s3": s3, "s4": s4, "s5": s5} + ) + divide(SP_O_function, instance=instance) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py index eb65213..9be3a3d 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py @@ -71,7 +71,23 @@ def TTC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger map_agent_to_best_item.pop(student, None) # Delete student if exists in the dict if __name__ == "__main__": - import doctest + import doctest, sys print("\n", doctest.testmod(), "\n") + sys.exit(1) + logger.addHandler(logging.StreamHandler()) + logger.setLevel(logging.INFO) + + from fairpyx.adaptors import divide + s1 = {"c1": 40, "c2": 20, "c3": 10, "c4": 30} + s2 = {"c1": 6, "c2": 20, "c3": 70, "c4": 4} + s3 = {"c1": 9, "c2": 20, "c3": 21, "c4": 50} + s4 = {"c1": 25, "c2": 5, "c3": 15, "c4": 55} + s5 = {"c1": 5, "c2": 90, "c3": 3, "c4": 2} + instance = Instance( + agent_capacities={"s1": 2, "s2": 2, "s3": 2, "s4": 2, "s5": 2}, + item_capacities={"c1": 3, "c2": 2, "c3": 2, "c4": 2}, + valuations={"s1": s1, "s2": s2, "s3": s3, "s4": s4, "s5": s5} + ) + divide(TTC_function, instance=instance) From 0e61dd2f3b628f72dc0a2e96576fd75f74a38cfe Mon Sep 17 00:00:00 2001 From: Erel Segal-Halevi Date: Tue, 11 Jun 2024 01:01:40 +0300 Subject: [PATCH 34/42] tests --- fairpyx/algorithms/Optimization_based_Mechanisms/OC.py | 2 +- fairpyx/algorithms/Optimization_based_Mechanisms/SP.py | 3 +-- fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py | 2 +- fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py | 2 +- fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py | 4 ++-- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py b/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py index 47112b0..b699f96 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py @@ -87,7 +87,7 @@ def OC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger if __name__ == "__main__": import doctest, sys print("\n", doctest.testmod(), "\n") - sys.exit(1) + # sys.exit(1) logger.addHandler(logging.StreamHandler()) logger.setLevel(logging.INFO) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py b/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py index 463f041..f5259ff 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py @@ -20,7 +20,6 @@ def SP_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger :param alloc: an allocation builder, which tracks the allocation and the remaining capacity for items and agents of the fair course allocation problem(CAP). - >>> from fairpyx.adaptors import divide >>> s1 = {"c1": 50, "c2": 49, "c3": 1} >>> s2 = {"c1": 48, "c2": 46, "c3": 6} @@ -121,7 +120,7 @@ def SP_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger if __name__ == "__main__": import doctest, sys print("\n", doctest.testmod(), "\n") - sys.exit(1) + # sys.exit(1) logger.addHandler(logging.StreamHandler()) logger.setLevel(logging.INFO) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py index 4f4fb6a..6ff5822 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py @@ -124,7 +124,7 @@ def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogge if __name__ == "__main__": import doctest, sys print("\n", doctest.testmod(), "\n") - sys.exit(1) + # sys.exit(1) logger.addHandler(logging.StreamHandler()) logger.setLevel(logging.INFO) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py index 9be3a3d..94f5767 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py @@ -73,7 +73,7 @@ def TTC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger if __name__ == "__main__": import doctest, sys print("\n", doctest.testmod(), "\n") - sys.exit(1) + # sys.exit(1) logger.addHandler(logging.StreamHandler()) logger.setLevel(logging.INFO) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py index 9b40cc6..88e7e80 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py @@ -63,7 +63,7 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg if __name__ == "__main__": import doctest, sys print("\n", doctest.testmod(), "\n") - sys.exit(1) + # sys.exit(1) logger.addHandler(logging.StreamHandler()) logger.setLevel(logging.INFO) @@ -72,7 +72,7 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg np.random.seed(2) instance = fairpyx.Instance.random_uniform( - num_of_agents=70, num_of_items=10, normalized_sum_of_values=100, + num_of_agents=10, num_of_items=5, normalized_sum_of_values=100, agent_capacity_bounds=[2, 6], item_capacity_bounds=[20, 40], item_base_value_bounds=[1, 1000], From 466dccaa22804461c05f768985e10198163edf70 Mon Sep 17 00:00:00 2001 From: MoriyaEster Date: Wed, 12 Jun 2024 21:35:02 +0300 Subject: [PATCH 35/42] clear some unnecessary files --- = | Bin 392 -> 0 bytes =1.12.0 | 2 -- 2 files changed, 2 deletions(-) delete mode 100644 = delete mode 100644 =1.12.0 diff --git a/= b/= deleted file mode 100644 index 12e498d68eba206d638d45db82a847bbb36d48f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 392 zcmZ{g%?iRm420(__zpelLBT)No_zw(1zBsY#cI{9e=o0o*@ECfmdz%U$t2nLYoSbA zIbBt%R-vPEMR*nVDo^%;XSfG-uu7~kE3imw7-^_CZUk0QaR|(angVX7o=zaC_Sl@* z2TilauX*oOHC%^g38$@=mb_=?D9C20X7G(|a~R^Z(UaKY*-V~Y3h<3$&{Q`)=={_Q zO-I%yv|TJ0EBS8d@#mUqsEKCyj;a$Xrs{|=1.21.6 in /home/moriyaester/Desktop/Autonomous-Robots/.venv/Scripts/python.exe/lib/python3.10/site-packages (from scipy) (1.26.4) From 3506d988111e766fbec4025ee97c79fe25a75d19 Mon Sep 17 00:00:00 2001 From: MoriyaEster Date: Wed, 12 Jun 2024 21:59:08 +0300 Subject: [PATCH 36/42] comments in the optimal_functions.py --- .../Optimization_based_Mechanisms/OC.py | 2 +- .../Optimization_based_Mechanisms/SP_O.py | 2 +- .../Optimization_based_Mechanisms/TTC_O.py | 2 +- .../Optimization_based_Mechanisms/__init__.py | 2 +- .../optimal_functions.py | 19 ++++++++++++------- fairpyx/algorithms/__init__.py | 2 +- 6 files changed, 17 insertions(+), 12 deletions(-) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py b/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py index 5576ef9..3985095 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py @@ -70,7 +70,7 @@ def OC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger # Check if the optimization problem was successfully solved if result_Z2 is not None: - optimal.alloctions(alloc, x, logger) + optimal.allocations(alloc, x, logger) optimal_value = problem.value explanation_logger.info("Optimal Objective Value:", optimal_value) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py index 306a36f..f579376 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py @@ -112,7 +112,7 @@ def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogge map_student_to_his_sum_bids[student] -= p_values[j] logger.info("student %s payed: %f", student, p_values[j]) logger.info("student %s have in dict: %f", student, map_student_to_his_sum_bids[student]) - optimal.alloctions(alloc, x, logger) + optimal.allocations(alloc, x, logger) optimal_value = problem.value logger.info("Optimal Objective Value: %d", optimal_value) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py index 81a4b55..c62bf37 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py @@ -51,7 +51,7 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg # Check if the optimization problem was successfully solved if result_Zt2 is not None: - optimal.alloctions(alloc, var, logger) + optimal.allocations(alloc, var, logger) optimal_value = problem.value explanation_logger.info("Optimal Objective Value:", optimal_value) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/__init__.py b/fairpyx/algorithms/Optimization_based_Mechanisms/__init__.py index 06614cf..70bdd4d 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/__init__.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/__init__.py @@ -3,5 +3,5 @@ from fairpyx.algorithms.Optimization_based_Mechanisms.SP import SP_function from fairpyx.algorithms.Optimization_based_Mechanisms.TTC_O import TTC_O_function from fairpyx.algorithms.Optimization_based_Mechanisms.TTC import TTC_function -from fairpyx.algorithms.Optimization_based_Mechanisms.optimal_functions import numberOfCourses, numberOfCourses, alloctions +from fairpyx.algorithms.Optimization_based_Mechanisms.optimal_functions import numberOfCourses, numberOfCourses, allocations diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/optimal_functions.py b/fairpyx/algorithms/Optimization_based_Mechanisms/optimal_functions.py index ae53f44..1351faf 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/optimal_functions.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/optimal_functions.py @@ -24,8 +24,8 @@ def numberOfCourses(var, alloc, less_then): return constraints - -def alloctions(alloc, var, logger): +# allocation course to student according the result of the linear programing +def allocations(alloc, var, logger): # Extract the optimized values of x x_values = var.value logger.info("x_values - the optimum allocation: %s", x_values) @@ -45,13 +45,16 @@ def alloctions(alloc, var, logger): alloc.give(student, course, logger) +# creating the rank matrix for the linear programing ((6) (17) in the article) using for TTC-O, SP-O and OC def createRankMat(alloc, logger): rank_mat = [[0 for _ in range(len(alloc.remaining_agents()))] for _ in range(len(alloc.remaining_items()))] + # sort the course to each student by the bids (best bids = higher rank) for i, student in enumerate(alloc.remaining_agents()): map_courses_to_student = alloc.remaining_items_for_agent(student) - sorted_courses = sorted(map_courses_to_student, key=lambda course: alloc.effective_value(student, course), ) + sorted_courses = sorted(map_courses_to_student, key=lambda course: alloc.effective_value(student, course)) + #fill the mat for j, course in enumerate(alloc.remaining_items()): if course in sorted_courses: rank_mat[j][i] = sorted_courses.index(course) + 1 @@ -59,9 +62,11 @@ def createRankMat(alloc, logger): logger.info("Rank matrix: %s", rank_mat) return rank_mat +# conditions (12) (16) in the article using only for the SP-O def SP_O_condition(alloc, result_Zt1, D, p, v): return result_Zt1 * D + cp.sum([alloc.remaining_item_capacities[course] * p[j] for j, course in enumerate(alloc.remaining_items())]) + cp.sum([v[i] for i, student in enumerate(alloc.remaining_agents())]) +# conditions (14) in the article using only for the SP-O def conditions_14(alloc, v,p): constraints = [] for j, course in enumerate(alloc.remaining_items()): @@ -70,14 +75,15 @@ def conditions_14(alloc, v,p): constraints.append(v[i] >= 0) return constraints +# sum the optimal rank to be sure the optimal bids agree with the optimal rank (6) (10) (17) (19) def sumOnRankMat(alloc, rank_mat, var): return cp.sum([rank_mat[j][i] * var[j, i] for j, course in enumerate(alloc.remaining_items()) for i, student in enumerate(alloc.remaining_agents()) if (student, course) not in alloc.remaining_conflicts]) -# if flag_if_use_alloc_in_func == 0 then using alloc.effective_value -# if flag_if_use_alloc_in_func == 1 then using effective_value_with_price +# if flag_if_use_alloc_in_func == 0 then using alloc.effective_value for TTC-O +# if flag_if_use_alloc_in_func == 1 then using effective_value_with_price for SP-O def roundTTC_O(alloc, logger, func, flag_if_use_alloc_in_func): rank_mat = createRankMat(alloc, logger) @@ -101,8 +107,7 @@ def roundTTC_O(alloc, logger, func, flag_if_use_alloc_in_func): [func(student, course) * x[j, i] if flag_if_use_alloc_in_func == 0 else func(alloc, student, course) * x[j, i] for j, course in enumerate(alloc.remaining_items()) for i, student in enumerate(alloc.remaining_agents()) - if (student, course) not in alloc.remaining_conflicts] - )) + if (student, course) not in alloc.remaining_conflicts])) constraints_Zt2 = notExceedtheCapacity(x, alloc) + numberOfCourses(x, alloc, 1) diff --git a/fairpyx/algorithms/__init__.py b/fairpyx/algorithms/__init__.py index a84b78d..a643a8c 100644 --- a/fairpyx/algorithms/__init__.py +++ b/fairpyx/algorithms/__init__.py @@ -7,4 +7,4 @@ from fairpyx.algorithms.Optimization_based_Mechanisms.SP import SP_function from fairpyx.algorithms.Optimization_based_Mechanisms.TTC_O import TTC_O_function from fairpyx.algorithms.Optimization_based_Mechanisms.TTC import TTC_function -from fairpyx.algorithms.Optimization_based_Mechanisms.optimal_functions import notExceedtheCapacity, numberOfCourses, alloctions +from fairpyx.algorithms.Optimization_based_Mechanisms.optimal_functions import notExceedtheCapacity, numberOfCourses, allocations From b83fb23129e02321e0ff3f2f4abac024cf329e2f Mon Sep 17 00:00:00 2001 From: MoriyaEster Date: Wed, 12 Jun 2024 22:25:42 +0300 Subject: [PATCH 37/42] added more test in the doctest --- .../algorithms/Optimization_based_Mechanisms/SP_O.py | 12 ++++++++++++ .../test_SP_O.py | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py index f579376..7a5d995 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py @@ -39,6 +39,18 @@ def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogge >>> instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) >>> divide(SP_O_function, instance=instance) {'s1': ['c2'], 's2': ['c1']} + + >>> s1 = {"c1": 40, "c2": 20, "c3": 10, "c4": 30} #{c1: 40, c4: 30, c2:20, c3: 10} + >>> s2 = {"c1": 6, "c2": 20, "c3": 70, "c4": 4} #{c3: 70, c2: 20, c1:6, c4: 4} + >>> s3 = {"c1": 9, "c2": 20, "c3": 21, "c4": 50} #{c4: 50, c3: 21, c2:20, c1: 9} + >>> s4 = {"c1": 25, "c2": 5, "c3": 15, "c4": 55} #{c4: 55, c1: 25, c3:15, c2: 5} + >>> s5 = {"c1": 5, "c2": 90, "c3": 3, "c4": 2} #{c2: 90, c1: 5, c3:3, c4: 2} + >>> agent_capacities={"s1": 2, "s2": 2, "s3": 2, "s4": 2, "s5": 2} + >>> item_capacities={"c1": 3, "c2": 2, "c3": 2, "c4": 2} + >>> valuations={"s1": s1, "s2": s2, "s3": s3, "s4": s4, "s5": s5} + >>> instance = Instance(agent_capacities=agent_capacities, item_capacities=item_capacities, valuations=valuations) + >>> divide(SP_O_function, instance=instance) + {'s1': ['c1', 'c2'], 's2': ['c1', 'c3'], 's3': ['c3', 'c4'], 's4': ['c1', 'c4'], 's5': ['c2']} """ explanation_logger.info("\nAlgorithm SP-O starts.\n") diff --git a/tests/Tests_for_Optimization-based_Mechanisms/test_SP_O.py b/tests/Tests_for_Optimization-based_Mechanisms/test_SP_O.py index b7da60b..30c2b64 100644 --- a/tests/Tests_for_Optimization-based_Mechanisms/test_SP_O.py +++ b/tests/Tests_for_Optimization-based_Mechanisms/test_SP_O.py @@ -50,6 +50,18 @@ def test_sub_round_within_sub_round(): assert fairpyx.divide(fairpyx.algorithms.SP_O_function, instance=instance) == {'s1': ['c3', 'c4'], 's2': ['c1', 'c3'], 's3': ['c2']}, "ERROR" +def test_for_Erel(): + s1 = {"c1": 40, "c2": 20, "c3": 10, "c4": 30} + s2 = {"c1": 6, "c2": 20, "c3": 70, "c4": 4} + s3 = {"c1": 9, "c2": 20, "c3": 21, "c4": 50} + s4 = {"c1": 25, "c2": 5, "c3": 15, "c4": 55} + s5 = {"c1": 5, "c2": 90, "c3": 3, "c4": 2} + instance = fairpyx.Instance( + agent_capacities={"s1": 2, "s2": 2, "s3": 2, "s4": 2, "s5": 2}, + item_capacities={"c1": 3, "c2": 2, "c3": 2, "c4": 2}, + valuations={"s1": s1, "s2": s2, "s3": s3, "s4": s4, "s5": s5} + ) + fairpyx.divide(fairpyx.algorithms.SP_O_function, instance=instance) def test_random(): for i in range(NUM_OF_RANDOM_INSTANCES): From 8e73b99956131f6bd6ca53736934723ec55b7659 Mon Sep 17 00:00:00 2001 From: MoriyaEster Date: Thu, 20 Jun 2024 11:01:21 +0300 Subject: [PATCH 38/42] check the effective_value does not -inf --- fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py index 7a5d995..a0110d3 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py @@ -90,7 +90,10 @@ def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogge # condition number 13 + 14: for j, course in enumerate(alloc.remaining_items()): for i, student in enumerate(alloc.remaining_agents()): - constraints_Wt1.append(p[j]+v[i]+rank_mat[j][i]*D >= alloc.effective_value(student,course)) + if (alloc.effective_value(student,course) < 0): + constraints_Wt1.append(p[j] + v[i] + rank_mat[j][i] * D >= 0) + else: + constraints_Wt1.append(p[j]+v[i]+rank_mat[j][i]*D >= alloc.effective_value(student,course)) condition_14 = optimal.conditions_14(alloc, v, p) constraints_Wt1 += condition_14 From 31df2c21c1fc984db86252e5e384188813b421f2 Mon Sep 17 00:00:00 2001 From: MoriyaEster Date: Thu, 20 Jun 2024 16:19:46 +0300 Subject: [PATCH 39/42] after shaat Kabala with erel --- .../Optimization_based_Mechanisms/SP_O.py | 19 ++++++++- .../Optimization_based_Mechanisms/TTC.py | 14 +++++++ .../Optimization_based_Mechanisms/TTC_O.py | 40 +++++++++++++------ .../optimal_functions.py | 4 +- .../test_SP_O.py | 2 +- .../test_TTC_O.py | 2 +- 6 files changed, 63 insertions(+), 18 deletions(-) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py index a0110d3..9174916 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py @@ -138,4 +138,21 @@ def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogge if __name__ == "__main__": import doctest - print(doctest.testmod()) \ No newline at end of file + print(doctest.testmod()) + + import numpy as np, fairpyx + + np.random.seed(1) + instance = fairpyx.Instance.random_uniform( + num_of_agents=70, num_of_items=10, normalized_sum_of_values=100, + agent_capacity_bounds=[2, 6], + item_capacity_bounds=[10, 20], + item_base_value_bounds=[1, 1000], + item_subjective_ratio_bounds=[0.5, 1.5] + ) + instance =fairpyx.Instance(valuations = {"s1": {"c1": 10}, "s2": {"c1": 11}}, item_capacities={"c1": 1}, agent_capacities={"s1": 1, "s2": 1}) + logger.addHandler(logging.StreamHandler()) + logger.setLevel(logging.INFO) + + allocation = fairpyx.divide(SP_O_function, instance=instance) + print(allocation) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py index eb65213..36c1edc 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py @@ -73,5 +73,19 @@ def TTC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger if __name__ == "__main__": import doctest print("\n", doctest.testmod(), "\n") + import numpy as np, fairpyx + np.random.seed(1) + instance = fairpyx.Instance.random_uniform( + num_of_agents=70, num_of_items=10, normalized_sum_of_values=100, + agent_capacity_bounds=[2, 6], + item_capacity_bounds=[20, 40], + item_base_value_bounds=[1, 1000], + item_subjective_ratio_bounds=[0.5, 1.5] + ) + logger.addHandler(logging.StreamHandler()) + logger.setLevel(logging.INFO) + + allocation = fairpyx.divide(TTC_function, instance=instance) + print(allocation) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py index c62bf37..6c02c3a 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py @@ -27,17 +27,17 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg the fair course allocation problem(CAP). - >>> from fairpyx.adaptors import divide - >>> s1 = {"c1": 50, "c2": 49, "c3": 1} - >>> s2 = {"c1": 48, "c2": 46, "c3": 6} - >>> agent_capacities = {"s1": 1, "s2": 1} # 2 seats required - >>> course_capacities = {"c1": 1, "c2": 1, "c3": 1} # 3 seats available - >>> valuations = {"s1": s1, "s2": s2} - >>> instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) - >>> divide(TTC_O_function, instance=instance) + # >>> from fairpyx.adaptors import divide + # >>> s1 = {"c1": 50, "c2": 49, "c3": 1} + # >>> s2 = {"c1": 48, "c2": 46, "c3": 6} + # >>> agent_capacities = {"s1": 1, "s2": 1} # 2 seats required + # >>> course_capacities = {"c1": 1, "c2": 1, "c3": 1} # 3 seats available + # >>> valuations = {"s1": s1, "s2": s2} + # >>> instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) + # >>> divide(TTC_O_function, instance=instance) {'s1': ['c2'], 's2': ['c1']} """ - explanation_logger.info("\nAlgorithm TTC-O starts.\n") + logger.info("\nAlgorithm TTC-O starts.\n") max_iterations = max(alloc.remaining_agent_capacities[agent] for agent in alloc.remaining_agents()) # the amount of courses of student with maximum needed courses logger.info("Max iterations: %d", max_iterations) @@ -54,12 +54,26 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg optimal.allocations(alloc, var, logger) optimal_value = problem.value - explanation_logger.info("Optimal Objective Value:", optimal_value) + logger.info("Optimal Objective Value: %s", optimal_value) # Now you can use this optimal value for further processing else: - explanation_logger.info("Solver failed to find a solution or the problem is infeasible/unbounded.") + logger.info("Solver failed to find a solution or the problem is infeasible/unbounded.") if __name__ == "__main__": - import doctest - print(doctest.testmod()) \ No newline at end of file + # import doctest + # print(doctest.testmod()) + + np.random.seed(1) + instance = fairpyx.Instance.random_uniform( + num_of_agents=70, num_of_items=10, normalized_sum_of_values=100, + agent_capacity_bounds=[2, 6], + item_capacity_bounds=[20, 40], + item_base_value_bounds=[1, 1000], + item_subjective_ratio_bounds=[0.5, 1.5] + ) + logger.addHandler(logging.StreamHandler()) + logger.setLevel(logging.INFO) + + allocation = fairpyx.divide(TTC_O_function, instance=instance) + print(allocation) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/optimal_functions.py b/fairpyx/algorithms/Optimization_based_Mechanisms/optimal_functions.py index 1351faf..49b8892 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/optimal_functions.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/optimal_functions.py @@ -84,7 +84,7 @@ def sumOnRankMat(alloc, rank_mat, var): # if flag_if_use_alloc_in_func == 0 then using alloc.effective_value for TTC-O # if flag_if_use_alloc_in_func == 1 then using effective_value_with_price for SP-O -def roundTTC_O(alloc, logger, func, flag_if_use_alloc_in_func): +def roundTTC_O(alloc, logger, agent_item_value_func, flag_if_use_alloc_in_func): rank_mat = createRankMat(alloc, logger) x = cvxpy.Variable((len(alloc.remaining_items()), len(alloc.remaining_agents())), boolean=True) @@ -104,7 +104,7 @@ def roundTTC_O(alloc, logger, func, flag_if_use_alloc_in_func): boolean=True) # Is there a func which zero all the matrix? objective_Zt2 = cp.Maximize(cp.sum( - [func(student, course) * x[j, i] if flag_if_use_alloc_in_func == 0 else func(alloc, student, course) * x[j, i] + [agent_item_value_func(student, course) * x[j, i] if flag_if_use_alloc_in_func == 0 else agent_item_value_func(alloc, student, course) * x[j, i] for j, course in enumerate(alloc.remaining_items()) for i, student in enumerate(alloc.remaining_agents()) if (student, course) not in alloc.remaining_conflicts])) diff --git a/tests/Tests_for_Optimization-based_Mechanisms/test_SP_O.py b/tests/Tests_for_Optimization-based_Mechanisms/test_SP_O.py index 30c2b64..0e975df 100644 --- a/tests/Tests_for_Optimization-based_Mechanisms/test_SP_O.py +++ b/tests/Tests_for_Optimization-based_Mechanisms/test_SP_O.py @@ -10,7 +10,7 @@ import fairpyx import numpy as np -NUM_OF_RANDOM_INSTANCES=10 +NUM_OF_RANDOM_INSTANCES=2 def test_optimal_change_result(): diff --git a/tests/Tests_for_Optimization-based_Mechanisms/test_TTC_O.py b/tests/Tests_for_Optimization-based_Mechanisms/test_TTC_O.py index cb70bba..5a41629 100644 --- a/tests/Tests_for_Optimization-based_Mechanisms/test_TTC_O.py +++ b/tests/Tests_for_Optimization-based_Mechanisms/test_TTC_O.py @@ -11,7 +11,7 @@ import numpy as np -NUM_OF_RANDOM_INSTANCES=10 +NUM_OF_RANDOM_INSTANCES=2 def test_optimal_change_result(): s1 = {"c1": 50, "c2": 49, "c3": 1} From 5252f71a771ac45f25969bcb67af2049e8aaa986 Mon Sep 17 00:00:00 2001 From: MoriyaEster Date: Sun, 23 Jun 2024 14:11:51 +0300 Subject: [PATCH 40/42] fix the optimal_functions --- .../Optimization_based_Mechanisms/SP_O.py | 79 ++++++++++++------- .../Optimization_based_Mechanisms/TTC_O.py | 43 +++++++++- .../optimal_functions.py | 54 ------------- .../test_SP_O.py | 9 +++ 4 files changed, 100 insertions(+), 85 deletions(-) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py index 9174916..4cf531e 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py @@ -9,6 +9,7 @@ import cvxpy as cp import logging import fairpyx.algorithms.Optimization_based_Mechanisms.optimal_functions as optimal +import fairpyx.algorithms.Optimization_based_Mechanisms.TTC_O as TTC_O logger = logging.getLogger(__name__) # global dict @@ -18,6 +19,15 @@ def effective_value_with_price(alloc, student, course): global map_student_to_his_sum_bids return alloc.effective_value(student, course) + map_student_to_his_sum_bids[student] +# conditions (14) in the article using only for the SP-O +def conditions_14(alloc, v,p): + constraints = [] + for j, course in enumerate(alloc.remaining_items()): + for i, student in enumerate(alloc.remaining_agents()): + constraints.append(p[j] >= 0) + constraints.append(v[i] >= 0) + return constraints + def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger = ExplanationLogger()): """ @@ -30,26 +40,26 @@ def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogge the fair course allocation problem(CAP). - >>> from fairpyx.adaptors import divide - >>> s1 = {"c1": 50, "c2": 49, "c3": 1} - >>> s2 = {"c1": 48, "c2": 46, "c3": 6} - >>> agent_capacities = {"s1": 1, "s2": 1} # 2 seats required - >>> course_capacities = {"c1": 1, "c2": 1, "c3": 1} # 3 seats available - >>> valuations = {"s1": s1, "s2": s2} - >>> instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) - >>> divide(SP_O_function, instance=instance) + #>>> from fairpyx.adaptors import divide + #>>> s1 = {"c1": 50, "c2": 49, "c3": 1} + #>>> s2 = {"c1": 48, "c2": 46, "c3": 6} + #>>> agent_capacities = {"s1": 1, "s2": 1} # 2 seats required + #>>> course_capacities = {"c1": 1, "c2": 1, "c3": 1} # 3 seats available + #>>> valuations = {"s1": s1, "s2": s2} + #>>> instance = Instance(agent_capacities=agent_capacities, item_capacities=course_capacities, valuations=valuations) + #>>> divide(SP_O_function, instance=instance) {'s1': ['c2'], 's2': ['c1']} - >>> s1 = {"c1": 40, "c2": 20, "c3": 10, "c4": 30} #{c1: 40, c4: 30, c2:20, c3: 10} - >>> s2 = {"c1": 6, "c2": 20, "c3": 70, "c4": 4} #{c3: 70, c2: 20, c1:6, c4: 4} - >>> s3 = {"c1": 9, "c2": 20, "c3": 21, "c4": 50} #{c4: 50, c3: 21, c2:20, c1: 9} - >>> s4 = {"c1": 25, "c2": 5, "c3": 15, "c4": 55} #{c4: 55, c1: 25, c3:15, c2: 5} - >>> s5 = {"c1": 5, "c2": 90, "c3": 3, "c4": 2} #{c2: 90, c1: 5, c3:3, c4: 2} - >>> agent_capacities={"s1": 2, "s2": 2, "s3": 2, "s4": 2, "s5": 2} - >>> item_capacities={"c1": 3, "c2": 2, "c3": 2, "c4": 2} - >>> valuations={"s1": s1, "s2": s2, "s3": s3, "s4": s4, "s5": s5} - >>> instance = Instance(agent_capacities=agent_capacities, item_capacities=item_capacities, valuations=valuations) - >>> divide(SP_O_function, instance=instance) + #>>> s1 = {"c1": 40, "c2": 20, "c3": 10, "c4": 30} #{c1: 40, c4: 30, c2:20, c3: 10} + #>>> s2 = {"c1": 6, "c2": 20, "c3": 70, "c4": 4} #{c3: 70, c2: 20, c1:6, c4: 4} + #>>> s3 = {"c1": 9, "c2": 20, "c3": 21, "c4": 50} #{c4: 50, c3: 21, c2:20, c1: 9} + #>>> s4 = {"c1": 25, "c2": 5, "c3": 15, "c4": 55} #{c4: 55, c1: 25, c3:15, c2: 5} + #>>> s5 = {"c1": 5, "c2": 90, "c3": 3, "c4": 2} #{c2: 90, c1: 5, c3:3, c4: 2} + #>>> agent_capacities={"s1": 2, "s2": 2, "s3": 2, "s4": 2, "s5": 2} + #>>> item_capacities={"c1": 3, "c2": 2, "c3": 2, "c4": 2} + #>>> valuations={"s1": s1, "s2": s2, "s3": s3, "s4": s4, "s5": s5} + #>>> instance = Instance(agent_capacities=agent_capacities, item_capacities=item_capacities, valuations=valuations) + #>>> divide(SP_O_function, instance=instance) {'s1': ['c1', 'c2'], 's2': ['c1', 'c3'], 's3': ['c3', 'c4'], 's4': ['c1', 'c4'], 's5': ['c2']} """ @@ -70,7 +80,7 @@ def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogge break logger.info("map_student_to_his_sum_bids : " + str(map_student_to_his_sum_bids)) - result_Zt1, result_Zt2, x, problem, rank_mat = optimal.roundTTC_O(alloc, logger, effective_value_with_price, 1) # This is the TTC-O round. + result_Zt1, result_Zt2, x, problem, rank_mat = TTC_O.roundTTC_O(alloc, logger, effective_value_with_price, 1) # This is the TTC-O round. optimal_value = problem.value logger.info("Optimal Objective Value: %d", optimal_value) @@ -81,7 +91,10 @@ def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogge p = cvxpy.Variable(len(alloc.remaining_items())) # list of price to each course v = cvxpy.Variable(len(alloc.remaining_agents())) # list of value to each student - linear_problem = optimal.SP_O_condition(alloc, result_Zt1, D, p, v) + # conditions (12) (16) in the article using only for the SP-O + linear_problem = (result_Zt1 * D + cp.sum([alloc.remaining_item_capacities[course] * + p[j] for j, course in enumerate(alloc.remaining_items())]) + + cp.sum([v[i] for i, student in enumerate(alloc.remaining_agents())])) # linear problem number 12: objective_Wt1 = cp.Minimize(linear_problem) @@ -95,12 +108,14 @@ def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogge else: constraints_Wt1.append(p[j]+v[i]+rank_mat[j][i]*D >= alloc.effective_value(student,course)) - condition_14 = optimal.conditions_14(alloc, v, p) + condition_14 = conditions_14(alloc, v, p) constraints_Wt1 += condition_14 problem = cp.Problem(objective_Wt1, constraints=constraints_Wt1) result_Wt1 = problem.solve() # This is the optimal value of program (12)(13)(14). + logger.info("result_Wt1 - the optimum price: %d", result_Wt1) + # linear problem number 15: objective_Wt2 = cp.Minimize(cp.sum([alloc.remaining_item_capacities[course] * p[j] for j, course in enumerate(alloc.remaining_items())])) @@ -119,7 +134,13 @@ def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogge if result_Wt2 is not None: x_values = x.value p_values = p.value + v_value = v.value + D_value = D.value logger.info("p values: " + str(p_values)) + logger.info("v values: " + str(v_value)) + logger.info("D values: " + str(D_value)) + logger.info("W1 values: %d", result_Wt1) + logger.info("W2 values: %d", result_Wt2) # Iterate over students and courses to pay the price of the course each student got for i, student in enumerate(alloc.remaining_agents()): for j, course in enumerate(alloc.remaining_items()): @@ -141,16 +162,14 @@ def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogge print(doctest.testmod()) import numpy as np, fairpyx - - np.random.seed(1) - instance = fairpyx.Instance.random_uniform( - num_of_agents=70, num_of_items=10, normalized_sum_of_values=100, - agent_capacity_bounds=[2, 6], - item_capacity_bounds=[10, 20], - item_base_value_bounds=[1, 1000], - item_subjective_ratio_bounds=[0.5, 1.5] + s1 = {"c1": 10} + s2 = {"c1": 11} + instance = fairpyx.Instance( + agent_capacities={"s1": 1, "s2": 1}, + item_capacities={"c1": 1}, + valuations={"s1": s1, "s2": s2} ) - instance =fairpyx.Instance(valuations = {"s1": {"c1": 10}, "s2": {"c1": 11}}, item_capacities={"c1": 1}, agent_capacities={"s1": 1, "s2": 1}) + logger.addHandler(logging.StreamHandler()) logger.setLevel(logging.INFO) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py index 6c02c3a..0a819d4 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py @@ -14,7 +14,48 @@ import cvxpy as cp logger = logging.getLogger(__name__) +# if flag_if_use_alloc_in_func == 0 then using alloc.effective_value for TTC-O +# if flag_if_use_alloc_in_func == 1 then using effective_value_with_price for SP-O +def roundTTC_O(alloc, logger, agent_item_value_func, flag_if_use_alloc_in_func): + rank_mat = optimal.createRankMat(alloc, logger) + x = cvxpy.Variable((len(alloc.remaining_items()), len(alloc.remaining_agents())), boolean=True) + + sum_rank = optimal.sumOnRankMat(alloc, rank_mat, x) + + objective_Zt1 = cp.Maximize(sum_rank) + + constraints_Zt1 = optimal.notExceedtheCapacity(x, alloc) + optimal.numberOfCourses(x, alloc, 1) + + problem = cp.Problem(objective_Zt1, constraints=constraints_Zt1) + result_Zt1 = problem.solve() # This is the optimal value of program (6)(7)(8)(9). + logger.info("result_Zt1 - the optimum ranking: %d", result_Zt1) + + # Write and solve new program for Zt2 (10)(11)(7)(8) + x = cvxpy.Variable((len(alloc.remaining_items()), len(alloc.remaining_agents())), + boolean=True) # Is there a func which zero all the matrix? + + objective_Zt2 = cp.Maximize(cp.sum( + [agent_item_value_func(student, course) * x[j, i] if flag_if_use_alloc_in_func == 0 else agent_item_value_func(alloc, student, course) * x[j, i] + for j, course in enumerate(alloc.remaining_items()) + for i, student in enumerate(alloc.remaining_agents()) + if (student, course) not in alloc.remaining_conflicts])) + + constraints_Zt2 = optimal.notExceedtheCapacity(x, alloc) + optimal.numberOfCourses(x, alloc, 1) + + constraints_Zt2.append(sum_rank == result_Zt1) + + try: + problem = cp.Problem(objective_Zt2, constraints=constraints_Zt2) + result_Zt2 = problem.solve() + logger.info("result_Zt2 - the optimum bids: %d", result_Zt2) + + except Exception as e: + logger.info("Solver failed: %s", str(e)) + logger.error("An error occurred: %s", str(e)) + raise + + return result_Zt1, result_Zt2, x, problem, rank_mat def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger = ExplanationLogger()): """ @@ -47,7 +88,7 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg logger.info("There are no more agents (%d) or items(%d) ", len(alloc.remaining_agent_capacities),len(alloc.remaining_item_capacities)) break - result_Zt1, result_Zt2, var, problem , rank_mat= optimal.roundTTC_O(alloc, logger, alloc.effective_value, 0) + result_Zt1, result_Zt2, var, problem , rank_mat = roundTTC_O(alloc, logger, alloc.effective_value, 0) # Check if the optimization problem was successfully solved if result_Zt2 is not None: diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/optimal_functions.py b/fairpyx/algorithms/Optimization_based_Mechanisms/optimal_functions.py index 49b8892..4298eae 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/optimal_functions.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/optimal_functions.py @@ -62,19 +62,6 @@ def createRankMat(alloc, logger): logger.info("Rank matrix: %s", rank_mat) return rank_mat -# conditions (12) (16) in the article using only for the SP-O -def SP_O_condition(alloc, result_Zt1, D, p, v): - return result_Zt1 * D + cp.sum([alloc.remaining_item_capacities[course] * p[j] for j, course in enumerate(alloc.remaining_items())]) + cp.sum([v[i] for i, student in enumerate(alloc.remaining_agents())]) - -# conditions (14) in the article using only for the SP-O -def conditions_14(alloc, v,p): - constraints = [] - for j, course in enumerate(alloc.remaining_items()): - for i, student in enumerate(alloc.remaining_agents()): - constraints.append(p[j] >= 0) - constraints.append(v[i] >= 0) - return constraints - # sum the optimal rank to be sure the optimal bids agree with the optimal rank (6) (10) (17) (19) def sumOnRankMat(alloc, rank_mat, var): return cp.sum([rank_mat[j][i] * var[j, i] @@ -82,45 +69,4 @@ def sumOnRankMat(alloc, rank_mat, var): for i, student in enumerate(alloc.remaining_agents()) if (student, course) not in alloc.remaining_conflicts]) -# if flag_if_use_alloc_in_func == 0 then using alloc.effective_value for TTC-O -# if flag_if_use_alloc_in_func == 1 then using effective_value_with_price for SP-O -def roundTTC_O(alloc, logger, agent_item_value_func, flag_if_use_alloc_in_func): - rank_mat = createRankMat(alloc, logger) - - x = cvxpy.Variable((len(alloc.remaining_items()), len(alloc.remaining_agents())), boolean=True) - - sum_rank = sumOnRankMat(alloc, rank_mat, x) - - objective_Zt1 = cp.Maximize(sum_rank) - - constraints_Zt1 = notExceedtheCapacity(x, alloc) + numberOfCourses(x, alloc, 1) - - problem = cp.Problem(objective_Zt1, constraints=constraints_Zt1) - result_Zt1 = problem.solve() # This is the optimal value of program (6)(7)(8)(9). - logger.info("result_Zt1 - the optimum ranking: %d", result_Zt1) - - # Write and solve new program for Zt2 (10)(11)(7)(8) - x = cvxpy.Variable((len(alloc.remaining_items()), len(alloc.remaining_agents())), - boolean=True) # Is there a func which zero all the matrix? - - objective_Zt2 = cp.Maximize(cp.sum( - [agent_item_value_func(student, course) * x[j, i] if flag_if_use_alloc_in_func == 0 else agent_item_value_func(alloc, student, course) * x[j, i] - for j, course in enumerate(alloc.remaining_items()) - for i, student in enumerate(alloc.remaining_agents()) - if (student, course) not in alloc.remaining_conflicts])) - - constraints_Zt2 = notExceedtheCapacity(x, alloc) + numberOfCourses(x, alloc, 1) - - constraints_Zt2.append(sum_rank == result_Zt1) - - try: - problem = cp.Problem(objective_Zt2, constraints=constraints_Zt2) - result_Zt2 = problem.solve() - logger.info("result_Zt2 - the optimum bids: %d", result_Zt2) - - except Exception as e: - logger.info("Solver failed: %s", str(e)) - logger.error("An error occurred: %s", str(e)) - raise - return result_Zt1, result_Zt2, x, problem, rank_mat diff --git a/tests/Tests_for_Optimization-based_Mechanisms/test_SP_O.py b/tests/Tests_for_Optimization-based_Mechanisms/test_SP_O.py index 0e975df..585a759 100644 --- a/tests/Tests_for_Optimization-based_Mechanisms/test_SP_O.py +++ b/tests/Tests_for_Optimization-based_Mechanisms/test_SP_O.py @@ -24,6 +24,15 @@ def test_optimal_change_result(): assert fairpyx.divide(fairpyx.algorithms.SP_O_function, instance=instance) == {'s1': ['c2'], 's2': ['c1']}, "ERROR" +def test_with_two_students_one_course(): + s1 = {"c1": 10} + s2 = {"c1": 11} + instance = fairpyx.Instance( + agent_capacities={"s1": 1, "s2": 1}, + item_capacities={"c1": 1}, + valuations={"s1": s1, "s2": s2} + ) + assert fairpyx.divide(fairpyx.algorithms.SP_O_function, instance=instance) == {'s1': [], 's2': ['c1']}, "ERROR" def test_optimal_improve_cardinal_and_ordinal_results(): s1 = {"c1": 50, "c2": 30, "c3": 20} From d3ab77d5f17f9bde524e491597697dac96b896b5 Mon Sep 17 00:00:00 2001 From: MoriyaEster Date: Mon, 24 Jun 2024 11:53:03 +0300 Subject: [PATCH 41/42] finish implication --- .../Optimization_based_Mechanisms/SP_O.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py index 4cf531e..bc8a9bb 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py @@ -23,9 +23,9 @@ def effective_value_with_price(alloc, student, course): def conditions_14(alloc, v,p): constraints = [] for j, course in enumerate(alloc.remaining_items()): - for i, student in enumerate(alloc.remaining_agents()): - constraints.append(p[j] >= 0) - constraints.append(v[i] >= 0) + constraints.append(p[j] >= 0) + for i, student in enumerate(alloc.remaining_agents()): + constraints.append(v[i] >= 0) return constraints @@ -114,7 +114,7 @@ def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogge problem = cp.Problem(objective_Wt1, constraints=constraints_Wt1) result_Wt1 = problem.solve() # This is the optimal value of program (12)(13)(14). - logger.info("result_Wt1 - the optimum price: %d", result_Wt1) + logger.info("result_Wt1 - the optimum Wt1: %s", result_Wt1) # linear problem number 15: objective_Wt2 = cp.Minimize(cp.sum([alloc.remaining_item_capacities[course] * p[j] @@ -128,7 +128,7 @@ def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogge problem = cp.Problem(objective_Wt2, constraints=constraints_Wt2) result_Wt2 = problem.solve() # This is the optimal price - logger.info("result_Wt2 - the optimum price: %d", result_Wt2) + logger.info("result_Wt2 - the optimum Wt2: %s", result_Wt2) # Check if the optimization problem was successfully solved if result_Wt2 is not None: @@ -139,8 +139,7 @@ def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogge logger.info("p values: " + str(p_values)) logger.info("v values: " + str(v_value)) logger.info("D values: " + str(D_value)) - logger.info("W1 values: %d", result_Wt1) - logger.info("W2 values: %d", result_Wt2) + # Iterate over students and courses to pay the price of the course each student got for i, student in enumerate(alloc.remaining_agents()): for j, course in enumerate(alloc.remaining_items()): From 2482a9df2e99cba66f80f743743361747ce608b2 Mon Sep 17 00:00:00 2001 From: Erel Segal-Halevi Date: Mon, 24 Jun 2024 18:58:48 +0300 Subject: [PATCH 42/42] loggers --- .../Optimization_based_Mechanisms/OC.py | 2 +- .../Optimization_based_Mechanisms/SP.py | 15 +++++++-- .../Optimization_based_Mechanisms/SP_O.py | 8 +++-- .../Optimization_based_Mechanisms/TTC.py | 31 +++++++++---------- .../Optimization_based_Mechanisms/TTC_O.py | 13 ++------ .../Optimization_based_Mechanisms/__init__.py | 2 +- .../optimal_functions.py | 8 ++--- fairpyx/algorithms/__init__.py | 2 +- 8 files changed, 43 insertions(+), 38 deletions(-) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py b/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py index c224e53..607f6b9 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/OC.py @@ -70,7 +70,7 @@ def OC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger # Check if the optimization problem was successfully solved if result_Z2 is not None: - optimal.allocations(alloc, x, logger) + optimal.give_items_according_to_allocation_matrix(alloc, x, logger) optimal_value = problem.value explanation_logger.info("Optimal Objective Value:", optimal_value) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py b/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py index f5259ff..db5a915 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/SP.py @@ -42,7 +42,7 @@ def SP_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger max_iterations = max(alloc.remaining_agent_capacities[agent] for agent in alloc.remaining_agents()) # the amount of courses of student with maximum needed courses logger.info("Max iterations: %d", max_iterations) for iteration in range(max_iterations): # External loop of algorithm: in each iteration, each student gets 1 seat (if possible). - logger.info("Iteration number: %d", iteration+1) + logger.info("\nIteration number: %d", iteration+1) if len(alloc.remaining_agent_capacities) == 0 or len(alloc.remaining_item_capacities) == 0: # check if all the agents got their courses or there are no more logger.info("There are no more agents (%d) or items(%d) ",len(alloc.remaining_agent_capacities), len(alloc.remaining_item_capacities)) break @@ -136,4 +136,15 @@ def SP_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger item_capacities={"c1": 3, "c2": 2, "c3": 2, "c4": 2}, valuations={"s1": s1, "s2": s2, "s3": s3, "s4": s4, "s5": s5} ) - divide(SP_function, instance=instance) + # divide(SP_function, instance=instance) + + np.random.seed(1) + instance = Instance.random_uniform( + num_of_agents=70, num_of_items=10, normalized_sum_of_values=100, + agent_capacity_bounds=[2, 6], + item_capacity_bounds=[10, 30], + item_base_value_bounds=[1, 1000], + item_subjective_ratio_bounds=[0.5, 1.5] + ) + + allocation = divide(SP_function, instance=instance) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py index 7ac9d7f..507e4b8 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/SP_O.py @@ -74,7 +74,7 @@ def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogge map_student_to_his_sum_bids = {s: 0 for s in alloc.remaining_agents()} for iteration in range(max_iterations): - logger.info("Iteration number: %d", iteration+1) + logger.info("\nIteration number: %d", iteration+1) if len(alloc.remaining_agent_capacities) == 0 or len(alloc.remaining_item_capacities) == 0: # check if all the agents got their courses or there are no more logger.info("There are no more agents (%d) or items(%d) ", len(alloc.remaining_agent_capacities),len(alloc.remaining_item_capacities)) break @@ -148,7 +148,7 @@ def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogge map_student_to_his_sum_bids[student] -= p_values[j] logger.info("student %s payed: %f", student, p_values[j]) logger.info("student %s have in dict: %f", student, map_student_to_his_sum_bids[student]) - optimal.allocations(alloc, x, logger) + optimal.give_items_according_to_allocation_matrix(alloc, x, logger) optimal_value = problem.value logger.info("Optimal Objective Value: %d", optimal_value) @@ -160,6 +160,7 @@ def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogge if __name__ == "__main__": import doctest, sys, numpy as np print("\n", doctest.testmod(), "\n") + from fairpyx.adaptors import divide s1 = {"c1": 10} s2 = {"c1": 11} @@ -170,9 +171,10 @@ def SP_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogge ) logger.addHandler(logging.StreamHandler()) logger.setLevel(logging.INFO) + allocation = divide(SP_O_function, instance=instance) + print(allocation,"\n\n\n") - from fairpyx.adaptors import divide s1 = {"c1": 40, "c2": 20, "c3": 10, "c4": 30} s2 = {"c1": 6, "c2": 20, "c3": 70, "c4": 4} s3 = {"c1": 9, "c2": 20, "c3": 21, "c4": 50} diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py index 868c631..80ea687 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC.py @@ -35,7 +35,7 @@ def TTC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger max_iterations = max(alloc.remaining_agent_capacities[agent] for agent in alloc.remaining_agents()) # the amount of courses of student with maximum needed courses logger.info("Max iterations: %d", max_iterations) for iteration in range(max_iterations): # External loop of algorithm: in each iteration, each student gets 1 seat (if possible). - logger.info("Iteration number: %d", iteration+1) + logger.info("\n Iteration number: %d", iteration+1) if len(alloc.remaining_agent_capacities) == 0 or len(alloc.remaining_item_capacities) == 0: # check if all the agents got their courses or there are no more courses with seats logger.info("There are no more agents (%d) or items(%d) ",len(alloc.remaining_agent_capacities), len(alloc.remaining_item_capacities)) break @@ -51,6 +51,7 @@ def TTC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger map_agent_to_best_item[current_agent] = max(potential_items_for_agent, key=lambda item: alloc.effective_value(current_agent, item)) # for each agent save the course with the max bids from the potential courses only + logger.debug("map_agent_to_best_item = %s", map_agent_to_best_item) for current_agent in agents_with_no_potential_items: # remove agents that don't need courses logger.info("Agent %s cannot pick any more items: remaining=%s, bundle=%s", current_agent, alloc.remaining_item_capacities, alloc.bundles[current_agent]) @@ -76,20 +77,21 @@ def TTC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger # sys.exit(1) logger.addHandler(logging.StreamHandler()) - logger.setLevel(logging.INFO) + logger.setLevel(logging.DEBUG) from fairpyx.adaptors import divide - s1 = {"c1": 40, "c2": 20, "c3": 10, "c4": 30} - s2 = {"c1": 6, "c2": 20, "c3": 70, "c4": 4} - s3 = {"c1": 9, "c2": 20, "c3": 21, "c4": 50} - s4 = {"c1": 25, "c2": 5, "c3": 15, "c4": 55} - s5 = {"c1": 5, "c2": 90, "c3": 3, "c4": 2} - instance = Instance( - agent_capacities={"s1": 2, "s2": 2, "s3": 2, "s4": 2, "s5": 2}, - item_capacities={"c1": 3, "c2": 2, "c3": 2, "c4": 2}, - valuations={"s1": s1, "s2": s2, "s3": s3, "s4": s4, "s5": s5} - ) - divide(TTC_function, instance=instance) + + # s1 = {"c1": 40, "c2": 20, "c3": 10, "c4": 30} + # s2 = {"c1": 6, "c2": 20, "c3": 70, "c4": 4} + # s3 = {"c1": 9, "c2": 20, "c3": 21, "c4": 50} + # s4 = {"c1": 25, "c2": 5, "c3": 15, "c4": 55} + # s5 = {"c1": 5, "c2": 90, "c3": 3, "c4": 2} + # instance = Instance( + # agent_capacities={"s1": 2, "s2": 2, "s3": 2, "s4": 2, "s5": 2}, + # item_capacities={"c1": 3, "c2": 2, "c3": 2, "c4": 2}, + # valuations={"s1": s1, "s2": s2, "s3": s3, "s4": s4, "s5": s5} + # ) + # divide(TTC_function, instance=instance) np.random.seed(1) instance = Instance.random_uniform( @@ -99,9 +101,6 @@ def TTC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger item_base_value_bounds=[1, 1000], item_subjective_ratio_bounds=[0.5, 1.5] ) - logger.addHandler(logging.StreamHandler()) - logger.setLevel(logging.INFO) allocation = divide(TTC_function, instance=instance) - print(allocation) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py index 2d65545..74991aa 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/TTC_O.py @@ -83,7 +83,7 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg max_iterations = max(alloc.remaining_agent_capacities[agent] for agent in alloc.remaining_agents()) # the amount of courses of student with maximum needed courses logger.info("Max iterations: %d", max_iterations) for iteration in range(max_iterations): - logger.info("Iteration number: %d", iteration+1) + logger.info("\nIteration number: %d", iteration+1) if len(alloc.remaining_agent_capacities) == 0 or len(alloc.remaining_item_capacities) == 0: # check if all the agents got their courses or there are no more logger.info("There are no more agents (%d) or items(%d) ", len(alloc.remaining_agent_capacities),len(alloc.remaining_item_capacities)) break @@ -92,7 +92,7 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg # Check if the optimization problem was successfully solved if result_Zt2 is not None: - optimal.allocations(alloc, var, logger) + optimal.give_items_according_to_allocation_matrix(alloc, var, logger) optimal_value = problem.value logger.info("Optimal Objective Value: %s", optimal_value) @@ -102,7 +102,7 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg if __name__ == "__main__": - import doctest, numpy as np + import doctest, sys, numpy as np print("\n", doctest.testmod(), "\n") # sys.exit(1) @@ -121,10 +121,3 @@ def TTC_O_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogg ) allocation = divide(TTC_O_function, instance=instance) fairpyx.validate_allocation(instance, allocation, title=f"Seed {5}, TTC_O_function") - divide(TTC_O_function, instance=instance) - - logger.addHandler(logging.StreamHandler()) - logger.setLevel(logging.INFO) - - allocation = fairpyx.divide(TTC_O_function, instance=instance) - print(allocation) diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/__init__.py b/fairpyx/algorithms/Optimization_based_Mechanisms/__init__.py index 70bdd4d..bee4e93 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/__init__.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/__init__.py @@ -3,5 +3,5 @@ from fairpyx.algorithms.Optimization_based_Mechanisms.SP import SP_function from fairpyx.algorithms.Optimization_based_Mechanisms.TTC_O import TTC_O_function from fairpyx.algorithms.Optimization_based_Mechanisms.TTC import TTC_function -from fairpyx.algorithms.Optimization_based_Mechanisms.optimal_functions import numberOfCourses, numberOfCourses, allocations +from fairpyx.algorithms.Optimization_based_Mechanisms.optimal_functions import numberOfCourses, numberOfCourses, give_items_according_to_allocation_matrix diff --git a/fairpyx/algorithms/Optimization_based_Mechanisms/optimal_functions.py b/fairpyx/algorithms/Optimization_based_Mechanisms/optimal_functions.py index 4298eae..44f7b7b 100644 --- a/fairpyx/algorithms/Optimization_based_Mechanisms/optimal_functions.py +++ b/fairpyx/algorithms/Optimization_based_Mechanisms/optimal_functions.py @@ -25,10 +25,10 @@ def numberOfCourses(var, alloc, less_then): # allocation course to student according the result of the linear programing -def allocations(alloc, var, logger): +def give_items_according_to_allocation_matrix(alloc, allocation_matrix, logger): # Extract the optimized values of x - x_values = var.value - logger.info("x_values - the optimum allocation: %s", x_values) + x_values = allocation_matrix.value + logger.info("x_values - the optimum allocation:\n%s", x_values) # Initialize a dictionary where each student will have an empty list assign_map_courses_to_student = {student: [] for student in alloc.remaining_agents()} @@ -59,7 +59,7 @@ def createRankMat(alloc, logger): if course in sorted_courses: rank_mat[j][i] = sorted_courses.index(course) + 1 - logger.info("Rank matrix: %s", rank_mat) + logger.debug("Rank matrix:\n%s", rank_mat) return rank_mat # sum the optimal rank to be sure the optimal bids agree with the optimal rank (6) (10) (17) (19) diff --git a/fairpyx/algorithms/__init__.py b/fairpyx/algorithms/__init__.py index a643a8c..32a3ed6 100644 --- a/fairpyx/algorithms/__init__.py +++ b/fairpyx/algorithms/__init__.py @@ -7,4 +7,4 @@ from fairpyx.algorithms.Optimization_based_Mechanisms.SP import SP_function from fairpyx.algorithms.Optimization_based_Mechanisms.TTC_O import TTC_O_function from fairpyx.algorithms.Optimization_based_Mechanisms.TTC import TTC_function -from fairpyx.algorithms.Optimization_based_Mechanisms.optimal_functions import notExceedtheCapacity, numberOfCourses, allocations +from fairpyx.algorithms.Optimization_based_Mechanisms.optimal_functions import notExceedtheCapacity, numberOfCourses, give_items_according_to_allocation_matrix