Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 15 additions & 0 deletions fairpy/course_allocation/Course.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from copy import deepcopy

class Course:
def __init__(self, name:str, price:float, max_capacity:int, capacity:int = 0) -> None:
self.name = name
self.price = price
self.capacity = capacity
self.max_capacity = max_capacity

def comperator(a, b):
return (a.capacity - a.max_capacity) - (b.capacity - b.max_capacity)


def __str__(self) -> str:
return f"course name: {self.name} capacity {self.capacity}/{self.max_capacity} and priced {self.price}"
20 changes: 20 additions & 0 deletions fairpy/course_allocation/Student.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from copy import deepcopy
from Course import Course
class Student:
def __init__(self,name, budget, preferences, courses = [], year=1):
self.name = name
self.budget = budget
self.courses = courses
self.preferences = preferences
self.year = year

def __str__(self) -> str:
return self.name + " year: " + str(self.year) + "with budget of (" + str(self.budget) + ") \n" + str(self.courses) + "\n " + str(self.preferences)

def student_courses(self):
return f"student : {self.name} and courses " + str([c.name for c in self.courses])

def comperator(a, b):
if(a.year - b.year == 0):
return (a.budget - b.budget)
return a.year - b.year
16 changes: 16 additions & 0 deletions fairpy/course_allocation/TabuList.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class TabuList:
def __init__(self, size):
self.size = size
self.items = []

def add(self, item):
if item not in self.items:
self.items.append(item)
while len(self.items) > self.size:
self.items.pop(0)

def __iter__(self):
return iter(self.items)



232 changes: 232 additions & 0 deletions fairpy/course_allocation/algorithm1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
'''
Course Match: A Large-Scale Implementation of Approximate Competitive Equilibrium from Equal Incomes for Combinatorial Allocation
Published by:Eric Budish, Gérard P. Cachon, Judd B. Kessler, Abraham Othmanba
Link: https://pubsonline.informs.org/doi/epdf/10.1287/opre.2016.1544
Heuristic search algorithm through price space, originally developed in Othman et al. 2010

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

יש להוסיף כאן (ובכל שאר הקבצים) את השם המלא של המאמר, שמות המחברים, וקישור למאמר.

designed to give each student a fixed budget at first and try find the must corresponding
price vector to approximate competitive equilibrium with lowest clearing error.
programmers : Aviv Danino & Ori Ariel
'''


from Course import Course
from Student import Student
from functools import cmp_to_key
from TabuList import TabuList
import numpy as np
import time
import random
import copy
import doctest


import logging
logger = logging.getLogger(__name__)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

כל השורות שמשנות את תצורת הלוג הגלובלית (בקובץ זה שורות 22-28, וכן בשאר הקבצים) לא צריכות להיות ביחידה, אלא רק בתוכנית הראשית.



def reset_update_prices(price_vector, courses):
'''
reset prices of courses and capacity
>>> a = Course(name='a', price=0, max_capacity=5)
>>> b = Course(name='b', price=0, max_capacity=3)
>>> c = Course(name='c', price=0, max_capacity=5)
>>> courses = [a, b, c]

>>> s1 = Student(name='s1', budget=15, preferences=([c, b]))
>>> s2 = Student(name='s2', budget=15, preferences=([b, c, a]))
>>> s3 = Student(name='s3', budget=15, preferences=([b, a]))
>>> s4 = Student(name='s4', budget=15, preferences=([a, c]))
>>> s5 = Student(name='s5', budget=15, preferences=([a, b, c]))
>>> students = [s1, s2, s3, s4, s5]
>>> max_budget = 15
>>> reset_update_prices([7,8,9], courses)
>>> [course.capacity for course in courses]
[0, 0, 0]
>>> [course.price for course in courses]
[7, 8, 9]
'''
i = 0
for course in courses:
course.capacity = 0
course.price = price_vector[i]
i += 1

def reset_students(students, max_budget: float):
'''
reset budget and courses for students
>>> a = Course(name='a', price=0, max_capacity=5)
>>> b = Course(name='b', price=0, max_capacity=3)
>>> c = Course(name='c', price=0, max_capacity=5)
>>> courses = [a, b, c]

>>> s1 = Student(name='s1', budget=0, preferences=([c, b]))
>>> s2 = Student(name='s2', budget=0, preferences=([b, c, a]))
>>> s3 = Student(name='s3', budget=0, preferences=([b, a]))
>>> s4 = Student(name='s4', budget=0, preferences=([a, c]))
>>> s5 = Student(name='s5', budget=0, preferences=([a, b, c]))
>>> students = [s1, s2, s3, s4, s5]
>>> max_budget = 15
>>> reset_students(students, max_budget)
>>> [student.budget for student in students]
[15, 15, 15, 15, 15]
>>> [student.courses for student in students]
[[], [], [], [], []]
'''
for student in students:
student.budget = max_budget
student.courses = []

def map_price_demand(price_vector, max_budget: float, students,
courses):
'''
****adding more documention about here
mapping price vector to demands of students
>>> a = Course(name='a', price=0, capacity = 0, max_capacity=5)
>>> b = Course(name='b', price=0, capacity = 0,max_capacity=3)
>>> c = Course(name='c', price=0, capacity = 0,max_capacity=5)
>>> courses = [a, b, c]

>>> s1 = Student(name='s1', budget=0, preferences=([c, b]))
>>> s2 = Student(name='s2', budget=0, preferences=([b, c, a]))
>>> s3 = Student(name='s3', budget=0, preferences=([b, a]))
>>> s4 = Student(name='s4', budget=0, preferences=([a, c]))
>>> s5 = Student(name='s5', budget=0, preferences=([a, b, c]))
>>> students = [s1, s2, s3, s4, s5]
>>> max_budget = 15
>>> price_vector = [7,8,7]
>>> map_price_demand(price_vector, max_budget, students, courses)
>>> [str(course) for course in courses]
['course name: a capacity 3/5 and priced 7', 'course name: b capacity 4/3 and priced 8', 'course name: c capacity 3/5 and priced 7']
'''
#different functions to each
#testing working for changes in function -- DELETE
reset_update_prices(price_vector, courses)
reset_students(students, max_budget)
def get_course(s:Student):
for preference in s.preferences:
if (s.budget >= preference.price and
preference not in s.courses):
s.courses.append(preference)
s.budget = s.budget - preference.price
preference.capacity += 1
for student in students:
if (len(student.preferences) > len(student.courses)):
get_course(student)

#function returning alpha error
def alpha_error(price_vector, max_budget, students, courses):
map_price_demand(price_vector, max_budget, students, courses)
sum = 0
for course in courses:
sum += (course.max_capacity - course.capacity)**2
return sum

#return list of string representation of demands
'''
>>> a = Course(name='a', price=0, capacity = 3, max_capacity=5)
>>> b = Course(name='b', price=0, capacity = 4,max_capacity=3)
>>> c = Course(name='c', price=0, capacity = 4,max_capacity=5)
>>> courses = [a, b, c]
>>> lst = get_demand_vector(courses)
>>> ["3/5", "4/3", "4/5"]
'''
def get_demand_vector(courses):
lst = []
for course in courses:
lst.append(f"{course.capacity}/{course.max_capacity}")
return lst


def algorithm1(students, courses, max_budget:float, time_to:float, seed:int = 3, counter = 0):
'''
Heuristic search algorithm through price space, originally developed in Othman et al. 2010
designed to give each student a fixed budget at first and try find the must corresponding
price vector to approximate competitive equilibrium with lowest clearing error.
programmers : Aviv Danino & Ori Ariel

counter : parameter designed to help for testing in different machines

>>> a = Course(name='a', price=0, capacity=0, max_capacity=5)
>>> b = Course(name='b', price=0, capacity=0, max_capacity=4)
>>> c = Course(name='c', price=0, capacity=0, max_capacity=5)
>>> courses = [a, b, c]
>>> s1 = Student(name='s1', budget=18, year=1, courses=[], preferences=([c, b]))
>>> s2 = Student(name='s2', budget=18, year=1, courses=[], preferences=([b, c, a]))
>>> s3 = Student(name='s3', budget=18, year=1, courses=[], preferences=([b, a]))
>>> s4 = Student(name='s4', budget=18, year=1, courses=[], preferences=([a, b, c]))
>>> s5 = Student(name='s5', budget=18, year=1, courses=[], preferences=([a, b, c]))
>>> s6 = Student(name='s6', budget=18, year=1, courses=[], preferences=([a, c, b]))
>>> students = [s1, s2, s3, s4, s5, s6]
>>> max_budget = 18
>>> algorithm1(students, courses, max_budget, time_to= 2.0, seed = 3)
[4.86, 2.34, 6.66]
'''
logger_counter = 0
if time_to < 0.01:
logger.warning("to little time can crash the program")
pStar = []
start_time = time.time()
best_error = float("inf")
#for better testing
random_state = np.random.RandomState(seed=3)
while(time.time() - start_time < time_to):
price_vector = [((random_state.randint(low=1,high=99)/100)*max_budget) for i in range(len(courses))]
search_error = alpha_error(price_vector, max_budget, students, courses)
tabu_list = TabuList(5)
c = 0
while c < tabu_list.size:
queue = []
for i in range(0, len(price_vector)):
temp = copy.deepcopy(price_vector)
temp[i] = (random_state.randint(low=1,high=99)/100)*max_budget
queue.append(temp)
queue = sorted(queue, key=lambda x: alpha_error(x, max_budget, students, courses))
found_step = False
while(queue and not found_step):#3
temp = queue.pop()
#need function for demand check in tabu list
map_price_demand(temp, max_budget, students, courses)
if get_demand_vector(courses) not in tabu_list:
logger.debug("%s", str(temp))
found_step = True
#end of while 3
if(not queue) : c = tabu_list.size
else:
price_vector = temp
#add the demands and not the price vector
current_error = alpha_error(price_vector, max_budget, students, courses)
demand_vector = get_demand_vector(courses)
tabu_list.add(demand_vector)
logger.debug(demand_vector)
if(current_error < search_error):
search_error = current_error
logger.debug("search error is %d", search_error)
c = 0
else:
c = c + 1
#end of if
if(current_error < best_error):
logger_counter += 1
best_error = current_error
pStar = price_vector
logger.info("best error is %d ", best_error)
logger.debug("and its price vector is %s", pStar)
if(logger_counter == counter):
time_to = 0
c = tabu_list.size

#end of if
#end of while 2
#end of while 1

logger.debug(logger_counter)
return pStar

algorithm1.logger = logger

if __name__=="__main__":
import pytest
#run algorithm and test of the algorithm
print(__file__)
# s1 = __file__[:-3] + "_test.py"
pytest.main(args=[__file__, __file__[:-3]+"_test.py"])
62 changes: 62 additions & 0 deletions fairpy/course_allocation/algorithm1_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from algorithm1 import algorithm1,Course,Student,random,TabuList,copy,map_price_demand,reset_update_prices,cmp_to_key,reset_students,time

def pretty_testing(lst:list):
return [round(item, 2) for item in lst]

def test1():
a = Course(name='a', price=0, max_capacity=5)
b = Course(name='b', price=0, max_capacity=3)
c = Course(name='c', price=0, max_capacity=5)
courses = [a, b, c]

s1 = Student(name='s1', budget=15, preferences=([c, b]))
s2 = Student(name='s2', budget=15, preferences=([b, c, a]))
s3 = Student(name='s3', budget=15, preferences=([b, a]))
s4 = Student(name='s4', budget=15, preferences=([a, c]))
s5 = Student(name='s5', budget=15, preferences=([a, b, c]))
students = [s1, s2, s3, s4, s5]
max_budget = 15
assert(pretty_testing(algorithm1(students, courses, max_budget, time_to= 1, seed = 3, counter=4)) == [4.05, 1.95, 5.55])

def test2():
a = Course(name='a', price=0, max_capacity=3)
b = Course(name='b', price=0, max_capacity=3)
c = Course(name='c', price=0, max_capacity=3)
courses = [a, b, c]
s1 = Student(name='s1', budget=10, courses=[], preferences=([a]))
s2 = Student(name='s2', budget=10, courses=[], preferences=([b,a]))
s3 = Student(name='s3', budget=10, courses=[], preferences=([c]))
s4 = Student(name='s4', budget=10, courses=[], preferences=([c,b]))
s5 = Student(name='s5', budget=10, courses=[], preferences=([c, a, b]))
students = [s1, s2, s3, s4, s5]

max_budget = 10
assert(pretty_testing(algorithm1(students, courses, max_budget, time_to= 1, seed = 3, counter=2)) == [2.7, 1.3, 3.7])

def test3():
a = Course(name='a', price=0, capacity=0, max_capacity=5)
b = Course(name='b', price=0, capacity=0, max_capacity=4)
c = Course(name='c', price=0, capacity=0, max_capacity=5)
d = Course(name='d', price=0, capacity=0, max_capacity=3)
e = Course(name='e', price=0, capacity=0, max_capacity=7)
f = Course(name='f', price=0, capacity=0, max_capacity=2)
courses = [a, b, c, d, e, f]
s1 = Student(name='s1', budget=18, year=1, courses=[], preferences=([c, b, e, f]))
s2 = Student(name='s2', budget=18, year=1, courses=[], preferences=([b, c, a, e]))
s3 = Student(name='s3', budget=18, year=1, courses=[], preferences=([b, a, d]))
s4 = Student(name='s4', budget=18, year=1, courses=[], preferences=([a, b, c]))
s5 = Student(name='s5', budget=18, year=1, courses=[], preferences=([d, a,e, b, c]))
s6 = Student(name='s6', budget=18, year=1, courses=[], preferences=([a, e,c,f, b]))
s7 = Student(name='s7', budget=18, year=1, courses=[], preferences=([c,d ,b]))
s8 = Student(name='s8', budget=18, year=1, courses=[], preferences=([b, c, a]))
s9 = Student(name='s9', budget=18, year=1, courses=[], preferences=([b,f,e, a]))
s10 = Student(name='s10', budget=18, year=1, courses=[], preferences=([d ,f]))
s11 = Student(name='s11', budget=18, year=1, courses=[], preferences=([d,e,f]))
s12 = Student(name='s12', budget=18, year=1, courses=[], preferences=([d,f,e]))
students = [s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12]
max_budget = 18
assert(pretty_testing(algorithm1(students, courses, max_budget, time_to= 1, seed = 3, counter = 3)) == [3.96, 12.6, 9.9, 4.68, 1.08, 4.14])#7

if __name__=="__main__":
import pytest
pytest.main(args=["fairpy/course_allocation"])

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

כמו שכתבתי לכם כבר (נראה לי): תשנו כאן ל:

pytest.main(args=[__file__])  

וכן בשאר הקבצים. כך שזה יעבוד על כל המחשבים בלי תלות במסלולים.

Loading