Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new five algorithems TTC, SP, TTC-O, SP-O, OC #4

Merged
merged 50 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
1b5e2db
adding our folders
MoriyaEster May 8, 2024
5200a74
init in algo
ofekats May 8, 2024
d004277
changed –remove_agent to remove_agent_from_loop
ofekats May 8, 2024
e916e25
changed –remove_agent to remove_agent_from_loop
ofekats May 8, 2024
28edbe9
cttc all tests passed sp not good yet
ofekats May 8, 2024
35eb2be
sp short and working
ofekats May 9, 2024
d4e1168
sp with comments
ofekats May 9, 2024
313d93d
sp move bids of courses with no potentioal- no longer have seats or h…
ofekats May 9, 2024
450c79a
fix test_from_the_atricle()
TamarBarIlan May 12, 2024
247e281
SP.py complete
TamarBarIlan May 12, 2024
9d99a81
adding comments
TamarBarIlan May 12, 2024
1e50489
start TTC-O
TamarBarIlan May 12, 2024
bc5ada1
TTC-O with mat Xij
ofekats May 13, 2024
cb35dba
TTC-O with new constraints
ofekats May 13, 2024
6fa782f
TTC-O work only in first round
MoriyaEster May 19, 2024
8378263
fix TTC-O. can do more than 1 iteration
TamarBarIlan May 21, 2024
1e5fb7d
TTC-O except the random test
MoriyaEster May 21, 2024
0a8850d
start implement SP-O
MoriyaEster May 26, 2024
2085d41
adding OC.py
TamarBarIlan May 28, 2024
f8b3598
start sp-o
MoriyaEster May 30, 2024
5d9bfe9
loogers
MoriyaEster Jun 4, 2024
ad8dc1a
looger for sp and ttc-o
MoriyaEster Jun 4, 2024
8826538
logger for all and outer functions
MoriyaEster Jun 4, 2024
ee35701
OC with give of its own
ofekats Jun 4, 2024
b591a52
Update OC.py
MoriyaEster Jun 4, 2024
cb3f163
logger for all and outer functions
MoriyaEster Jun 4, 2024
4b85078
logger for all and outer functions
MoriyaEster Jun 4, 2024
3164117
adding test running in test_OC
TamarBarIlan Jun 4, 2024
f105584
changed according Erel comments
MoriyaEster Jun 6, 2024
ea4630e
SP-O completed
ofekats Jun 9, 2024
b1c85c2
logger for all and outer functions
MoriyaEster Jun 10, 2024
e02992d
activate doctests
erelsgl Jun 10, 2024
06268be
Merge branch 'main' of github.com:Final-Project-fairpyx/fairpyx
erelsgl Jun 10, 2024
c7e7402
add main
erelsgl Jun 10, 2024
0e61dd2
tests
erelsgl Jun 10, 2024
85fde2f
Merge branch 'ariel-research:main' into main
MoriyaEster Jun 12, 2024
466dcca
clear some unnecessary files
MoriyaEster Jun 12, 2024
3506d98
comments in the optimal_functions.py
MoriyaEster Jun 12, 2024
0f29143
Merge remote-tracking branch 'origin/main'
MoriyaEster Jun 12, 2024
b83fb23
added more test in the doctest
MoriyaEster Jun 12, 2024
c50a9ad
Merge branch 'main' of github.com:Final-Project-fairpyx/fairpyx
erelsgl Jun 12, 2024
8e73b99
check the effective_value does not -inf
MoriyaEster Jun 20, 2024
168f32b
Merge branch 'main' of github.com:Final-Project-fairpyx/fairpyx
erelsgl Jun 20, 2024
31df2c2
after shaat Kabala with erel
MoriyaEster Jun 20, 2024
cfbc470
Merge branch 'ariel-research:main' into main
MoriyaEster Jun 23, 2024
5252f71
fix the optimal_functions
MoriyaEster Jun 23, 2024
d3ab77d
finish implication
MoriyaEster Jun 24, 2024
67beb5a
Merge branch 'main' of github.com:Final-Project-fairpyx/fairpyx into …
erelsgl Jun 24, 2024
2482a9d
loggers
erelsgl Jun 24, 2024
54c87f4
Merge branch 'main' of github.com:Final-Project-fairpyx/fairpyx
erelsgl Jun 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions fairpyx/algorithms/Optimization_based_Mechanisms/OC.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
"""
"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
"""

import cvxpy

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__)


def OC_function(alloc: AllocationBuilder, explanation_logger: ExplanationLogger = ExplanationLogger()):
"""
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']}
"""

explanation_logger.info("\nAlgorithm OC starts.\n")

x = cvxpy.Variable((len(alloc.remaining_items()), len(alloc.remaining_agents())), boolean=True)

rank_mat = optimal.createRankMat(alloc,logger)

sum_rank = optimal.sumOnRankMat(alloc, rank_mat, x)

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()
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[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 = 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.give_items_according_to_allocation_matrix(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("\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(OC_function, instance=instance)
150 changes: 150 additions & 0 deletions fairpyx/algorithms/Optimization_based_Mechanisms/SP.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
"""
"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 numpy as np
import fairpyx

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")

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()} # 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("\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
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 = 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
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 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) #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
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)

# 2. Allocate the remaining seats in each 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:
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: 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
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
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) # remove the course from the dict because the student get this course


if __name__ == "__main__":
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)

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)
Loading
Loading