views:

36

answers:

1

Background
I have a bunch of students, their desired projects and the supervisors for the respective projects. I'm running a battery of simulations to see which projects the students end up with, which will allow me to get some useful statistics required for feedback. So, this is essentially a Monte-Carlo simulation where I'm randomising the list of students and then iterating through it, allocating projects until I hit the end of the list. Then the process is repeated again.

Note that, within a single session, after each successful allocation of a project the following take place:
+ the project is set to allocated and cannot be given to another student
+ the supervisor has a fixed quota of students he can supervise. This is decremented by 1
+ Once the quota hits 0, all the projects from that supervisor become blocked and this has the same effect as a project being allocated

Code

def resetData():
    for student in students.itervalues():
        student.allocated_project = None

    for supervisor in supervisors.itervalues():
        supervisor.quota = 0

    for project in projects.itervalues():
        project.allocated = False
        project.blocked = False

The role of resetData() is to "reset" certain bits of the data. For example, when a project is successfully allocated, project.allocated for that project is flipped to True. While that's useful for a single run, for the next run I need to be deallocated.

Above I'm iterating through thee three dictionaries - one each for students, projects and supervisors - where the information is stored.

The next bit is the "Monte-Carlo" simulation for the allocation algorithm.

sesh_id = 1

for trial in range(50):

    for id in randomiseStudents(1):
        stud_id = id
        student = students[id]
        if not student.preferences:
        # Ignoring the students who've not entered any preferences

            for rank in ranks:
                temp_proj = random.choice(list(student.preferences[rank]))

                if not (temp_proj.allocated or temp_proj.blocked):
                    alloc_proj = student.allocated_proj_ref = temp_proj.proj_id
                    alloc_proj_rank = student.allocated_rank = rank
                    successActions(temp_proj)
                    temp_alloc = Allocated(sesh_id, stud_id, alloc_proj, alloc_proj_rank)
                    print temp_alloc # Explained
                    break                  
    sesh_id += 1
    resetData()  # Refer to def resetData() above

All randomiseStudents(1) does is randomise the order of students.

Allocated is a class defined as such:

class Allocated(object):
    def __init__(self, sesh_id, stud_id, alloc_proj, alloc_proj_rank):
        self.sesh_id = sesh_id
        self.stud_id = stud_id
        self.alloc_proj = alloc_proj
        self.alloc_proj_rank = alloc_proj_rank

   def __repr__(self):
        return str(self)

   def __str__(self):
        return "%s - Student: %s (Project: %s - Rank: %s)" %(self.sesh_id, self.stud_id, self.alloc_proj, self.alloc_proj_rank)

Output and problem

Now if I run this I get an output such as this (truncated):

1 - Student: 7720 (Project: 1100241 - Rank: 1)
1 - Student: 7832 (Project: 1100339 - Rank: 1)
1 - Student: 7743 (Project: 1100359 - Rank: 1)
1 - Student: 7820 (Project: 1100261 - Rank: 2)
1 - Student: 7829 (Project: 1100270 - Rank: 1)
.
.
.
1 - Student: 7822 (Project: 1100280 - Rank: 1)
1 - Student: 7792 (Project: 1100141 - Rank: 7)
2 - Student: 7739 (Project: 1100267 - Rank: 1)
3 - Student: 7806 (Project: 1100272 - Rank: 1)
.
.
.
45 - Student: 7806 (Project: 1100272 - Rank: 1)
46 - Student: 7714 (Project: 1100317 - Rank: 1)
47 - Student: 7930 (Project: 1100343 - Rank: 1)
48 - Student: 7757 (Project: 1100358 - Rank: 1)
49 - Student: 7759 (Project: 1100269 - Rank: 1)
50 - Student: 7778 (Project: 1100301 - Rank: 1)

Basically, it works perfectly for the first run, but on subsequent runs leading upto the nth run, in this case 50, only a single student-project allocation pair is returned.

Thus, the main issue I'm having trouble with is figuring out what is causing this anomalous behaviour especially since the first run works smoothly.

Thanks in advance,

Az

A: 

Do you really intend to set the supervisor's quota to 0 in resetData()? Doesn't that mean all their projects are now blocked?

Quoth the raven:

The supervisor has a fixed quota of students he can supervise. This is decremented by 1. Once the quota hits 0, all the projects from that supervisor become blocked and this has the same effect as a project being allocated.

If that's not it, you should check the output of randomiseStudents() to ensure it's returning a full list. It is, after all, the controlling item for that inner loop.


Update based on comments:

It appears the problem was that you were setting the supervisor's quota to 0 thus rendering all their projects blocked.

This is pure guesswork on my part but you probably got one student out in each iteration because the checking of all supervisors happened after an allocation. In that case, the one just allocated would have a quota of -1 and all the other quotas would be 0, effectively stopping all allocations after that one.

Ideally, you'd want to set the supervisor's quota back to their original value in resetData(). If it were a fixed quota (same for each supervisor), you could simply use:

for supervisor in supervisors.itervalues():
    supervisor.quota = 7 # for example

If each supervisor had a different quota, you would need to store that along with the other information (during initialisation but not resetting) as, for, example supervisor.start_quota. Then you could use:

for supervisor in supervisors.itervalues():
    supervisor.quota = supervisor.start_quota
paxdiablo
Hahahahahhahaha! Nice one! I can't believe I typed all that out for such a simple error. Thanks dude!
Az
Hindsight is 20/20 vision :-) Y'know I always wondered where that term came from, and looked it up one day. It actually means you can see at 20 feet what a normal human sees at 20 feet. Hence eagles have 80/20 vision, they can see at 80 feet the level of detail we can see at 20 and why they're so good at spotting field mice and such. Just a bit of education for those interested.
paxdiablo
Oh dear, this is a more serious issue than I thought. Each supervisor has an individual quota so I can't just reset it to a fixed value...
Az
@Az, you should store each supervisor's starting quota in their structure as well. Then in resetData(), just do: `supervisor.quota = supervisor.start_quota`
paxdiablo
@paxdiablo Ah, of course. I was doing that with a component for projects - can't believe I couldn't remember. That's twice you've saved me mate. I'm extremely grateful. PS - The 20/20 vision bite was interesting. I always thought it was funny in terms of the index of lens. 20± means you're pretty much blind :)
Az