3 # Copyright 2009 the Melange authors.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 """Slot allocation logic.
21 '"Sverre Rabbelier" <sverre@rabbelier.nl>',
29 class Error(Exception):
30 """Error class for the Allocation module.
36 class Allocator(object):
37 """A simple student slots allocator.
39 The buildSets method is used to validate the allocation data as well as
40 construct the sets that the algorithm then uses to distribute the slots.
41 By separating these steps it is possible to write a different allocation
42 algorithm but re-use the sets and validation logic.
45 # I tried to write explicit code that does not require any
46 # additional comments (with the exception of the set notation for
47 # the convenience of any mathematicians that happen to read this
50 def __init__(self
, orgs
, applications
, slots
, max_slots_per_org
):
51 """Initializes the allocator.
54 orgs: a list of all the orgs that need to be allocated
55 applications: a dictionary with for each org a list of applicants
56 slots: the total amount of available slots
61 for _
, value
in applications
.iteritems():
62 all_applications
+= value
64 self
.locked_slots
= {}
65 self
.adjusted_slots
= {}
66 self
.adjusted_orgss
= []
68 self
.unlocked_applications
= []
70 self
.max_slots_per_org
= max_slots_per_org
72 self
.applications
= applications
73 self
.all_applications
= set(all_applications
)
75 def allocate(self
, locked_slots
, adjusted_slots
):
76 """Allocates the slots and returns the result.
79 locked_slots: a dict with orgs and the number of slots they get
80 adjusted_slots: a dict with orgs and the number of extra slots they get
83 self
.locked_slots
= locked_slots
84 self
.adjusted_slots
= adjusted_slots
88 return self
.iterativeAllocation()
91 """Allocates slots with the specified constraints
95 all_applications
= self
.all_applications
96 locked_slots
= self
.locked_slots
97 adjusted_slots
= self
.adjusted_slots
100 locked_orgs
= set(locked_slots
.keys())
101 adjusted_orgs
= set(adjusted_slots
.keys())
104 # unlocked_orgs = self.orgs.difference(locked_orgs)
105 # unadjusted_orgs = self.orgs.difference(adjusted_orgs)
108 locked_and_adjusted_orgs
= locked_orgs
.intersection(adjusted_orgs
)
110 # unlocked_and_unadjusted_orgs = unlocked_orgs.intersection(unadjusted_orgs)
112 # a+o and b+o should be o
113 locked_orgs_or_orgs
= self
.orgs
.union(locked_orgs
)
114 adjusted_orgs_or_orgs
= self
.orgs
.union(adjusted_orgs
)
116 # an item can be only a or b, so a*b should be empty
117 if locked_and_adjusted_orgs
:
118 raise Error("Cannot have an org locked and adjusted")
120 # a+o should be o, testing length is enough though
121 if len(locked_orgs_or_orgs
) != len(self
.orgs
):
122 raise Error("Unknown org as locked slot")
125 if len(adjusted_orgs_or_orgs
) != len(self
.orgs
):
126 raise Error("Unknown org as adjusted slot")
129 locked_applications
= set(itertools
.chain(*locked_slots
.keys()))
130 unlocked_applications
= all_applications
.difference(locked_applications
)
132 self
.adjusted_orgss
= adjusted_orgs
133 self
.locked_orgs
= locked_orgs
134 self
.unlocked_applications
= unlocked_applications
136 def iterativeAllocation(self
):
137 """A simple iterative algorithm.
140 adjusted_orgs
= self
.adjusted_orgss
141 adjusted_slots
= self
.adjusted_slots
142 locked_orgs
= self
.locked_orgs
143 locked_slots
= self
.locked_slots
144 unlocked_applications
= self
.unlocked_applications
146 unlocked_applications_count
= len(unlocked_applications
)
147 unallocated_applications_count
= unlocked_applications_count
149 available_slots
= self
.slots
152 for org
in self
.orgs
:
153 org_applications
= self
.applications
[org
]
154 org_applications_count
= len(org_applications
)
156 if org
in locked_orgs
:
157 slots
= locked_slots
[org
]
159 weight
= float(org_applications_count
) / unallocated_applications_count
160 slots
= int(math
.floor(weight
*available_slots
))
162 if org
in adjusted_orgs
:
163 slots
+= adjusted_slots
[org
]
165 slots
= min(slots
, self
.max_slots_per_org
)
166 slots
= min(slots
, org_applications_count
)
167 slots
= min(slots
, available_slots
)
169 allocations
[org
] = slots
170 available_slots
-= slots
171 unallocated_applications_count
-= org_applications_count