Esempio n. 1
0
def findReplacement(speaker, date, specialization, piH, schedule):
    for dateA in sorted(schedule):
        if dateA <= date:
            continue
        for i, speakerA in enumerate(schedule[dateA]):
            if speakerA == speaker:
                continue
            slot = '%s,%d' % (dateA, i)
            spec = g_.node[slot]['specialization']
            if spec != specialization:
                continue

            thisPI = speakers_[speakerA]['pi_or_host']

            if thisPI == piH:
                continue

            # final check. Make sure the AWS does not come too early.
            nDays = (toDate(dateA) - toDate(date)).days
            prevAWS = g_.node[speakerA]['last_date']
            newDate = prevAWS - datetime.timedelta(days=nDays)
            if (newDate - prevAWS).days < 400:
                _logger.warn('Costly swapping. ignoring...')
                continue
            return speakerA, dateA
    _logger.warn('Could not find replacement for %s.%s' % (speaker, date))
    return None
Esempio n. 2
0
def main(outfile):
    global db_
    _logger.info('Scheduling AWS')
    getAllAWSPlusUpcoming()
    ans = None
    try:
        construct_flow_graph()
        ans = computeSchedule()
    except Exception as e:
        _logger.warn("Failed to schedule. Error was %s" % e)
    try:
        print_schedule(ans, outfile)
    except Exception as e:
        _logger.error("Could not print schedule. %s" % e)

    if ans:
        commit_schedule(ans)
    else:
        print('Failed to compute schedule')
        return -1
    try:
        write_graph()
    except Exception as e:
        _logger.error("Could not write graph to file")
        _logger.error("\tError was %s" % e)
    db_.close()
Esempio n. 3
0
def main(outfile):
    global db_
    _logger.info('Scheduling AWS')
    getAllAWSPlusUpcoming()
    ans = None
    try:
        construct_flow_graph()
        ans = computeSchedule()
    except Exception as e:
        _logger.warn("Failed to schedule. Error was %s" % e)

    # update specialization on g_ nodes.
    for date in ans:
        specs = [specialization_.get(x, 'UNSPECIFIED') for x in ans[date]]
        mostCommonSpec, freq = Counter(specs).most_common(1)[0]
        for slot in range(3):
            slot = '%s,%d' % (date, slot)
            if slot in g_:
                g_.node[slot]['specialization'] = mostCommonSpec

    ans = group_schedule(ans)
    ans = aws_helper.no_common_labs(ans)

    if ans:
        commit_schedule(ans)
    else:
        print('Failed to compute/commit schedule')
        return -1

    try:
        print_schedule(ans, outfile)
    except Exception as e:
        _logger.error("Could not print schedule. %s" % e)

    try:
        write_graph()
    except Exception as e:
        _logger.error("Could not write graph to file")
        _logger.error("\tError was %s" % e)
    db_.close()
Esempio n. 4
0
def no_common_labs(schedule, nweeks=2, ncalls=0):
    # Make sure that first 2 week entries have different PIs.
    if ncalls > 100:
        _logger.warn("Terminated after 100 calls")
        return schedule
    failedDates = []
    sortedDates = sorted(schedule)
    for ix, date in enumerate(sortedDates[:nweeks]):
        labs = []
        for i, speaker in enumerate(schedule[date]):
            spec = g_.node['%s,%d' % (date, i)]['specialization']
            piH = speakers_[speaker]['pi_or_host']
            if piH in labs:
                spec = specialization_.get(speaker, 'UNSPECIFIED')
                replaceWith = findReplacement(speaker, date, spec, piH,
                                              schedule)
                if replaceWith is not None:
                    speakerB, dateB = replaceWith
                    speakerA, dateA = speaker, date
                    schedule[dateA].append(speakerB)
                    schedule[dateA].remove(speakerA)
                    schedule[dateB].append(speakerA)
                    schedule[dateB].remove(speakerB)
                    _logger.info('Swapping %s and %s' % (speakerA, speakerB))
                else:
                    # swap this row by next and try again.
                    failedDates.append(date)
                    _logger.info("Failed to find alternative for %s" % date)
                    # swap this date with someone else.
                    for iy, datey in enumerate(sortedDates[nweeks:]):
                        if len(schedule[datey]) == len(schedule[date]):
                            # we can swap with entry.
                            temp = schedule[datey]
                            schedule[datey] = schedule[date]
                            schedule[date] = temp
                            return no_common_labs(schedule, nweeks, ncalls + 1)
            else:
                labs.append(piH)

    for fd in failedDates:
        _logger.warn('Entry for date %s has multiple speakers from same lab' %
                     fd)
        _logger.warn('Moving whole row down to more than %d positions' %
                     nweeks)

    return schedule
Esempio n. 5
0
def construct_flow_graph():
    """This is the most critical section of this task. It is usually good if
    flow graph is constructed to honor policy as much as possible rather than
    fixing the solution later.

    One important scheduling aim is to minimize number of freshers on the same
    day. Ideally no more than 1 freshers should be allowed on same day. This can
    be achieved by modifying the solution later : swapping freshers with
    experienced speaker from other days. We avoid that by drawing two edges 
    from freshers to a 'date' i.e. maximum of 2 slots can be filled by freshers.
    For others we let them fill all three slots.
    """

    g_.add_node('source', pos=(0, 0))
    g_.add_node('sink', pos=(10, 10))

    lastDate = None
    freshers = set()
    for i, speaker in enumerate(speakers_):
        # Last entry is most recent
        if speaker not in aws_.keys():
            # We are here because this speaker has not given any AWS yet.
            freshers.add(speaker)

            # If this user has PHD/POSTDOC, or INTPHD title. We create  a dummy
            # last date to bring her into potential speakers.
            # First make sure, I have their date of joining. Otherwise I
            # can't continue. For MSc By Research/INTPHD assign their first AWS
            # after 18 months. For PHD and POSTDOC, it should be after 12 months.
            if speakers_[speaker]['title'] == 'INTPHD':
                # InPhd should get their first AWS after 15 months of
                # joining.
                _logger.info('%s = INTPHD with 0 AWS so far' % speaker)
                joinDate = speakers_[speaker]['joined_on']
                if not joinDate:
                    _logger.warn("Could not find joining date")
                else:
                    lastDate = monthdelta(joinDate, +6)

            if speakers_[speaker]['title'] == 'MSC':
                # MSc should get their first AWS after 18 months of
                # joining. Same as INTPHD
                _logger.info('%s = MSC BY RESEARCH with 0 AWS so far' %
                             speaker)
                joinDate = speakers_[speaker]['joined_on']
                if not joinDate:
                    _logger.warn("Could not find joining date")
                else:
                    lastDate = monthdelta(joinDate, +6)

            elif speakers_[speaker]['title'] in ['PHD', 'POSTDOC']:
                joinDate = speakers_[speaker]['joined_on']
                _logger.info('%s PHD/POSTDOC with 0 AWS so far' % speaker)
                if not joinDate:
                    _logger.warn("Could not find joining date")
                else:
                    try:
                        # if datetime.
                        lastDate = joinDate.date()
                    except Exception as e:
                        # Else its date
                        lastDate = joinDate
        else:
            # We are here because this speaker has given AWS before
            # If this speaker is already on upcoming AWS list, ignore it.
            if speaker in upcoming_aws_:
                _logger.info('Speaker %s is already scheduled on %s' %
                             (speaker, upcoming_aws_[speaker]))
                continue
            # If this speakers is MSC by research and has given AWS before, she/he
            # need not give another.
            elif speakers_[speaker]['title'] == 'MSC':
                _logger.info('%s is MSC and has given AWS in the past' %
                             speaker)
                continue
            else:
                lastDate = aws_[speaker][-1]['date']

        # If a speaker has a lastDate either because he has given AWS in the
        # past or becuase she is fresher. Create an edge.
        if lastDate:
            g_.add_node(speaker, last_date=lastDate, pos=(1, 3 * i))
            g_.add_edge('source', speaker, capacity=1, weight=0)

    # Compute totalWeeks of schedule starting today.
    totalWeeks = int(365 / 7.0)
    today = datetime.date.today()
    nextMonday = today + datetime.timedelta(days=-today.weekday(), weeks=1)
    slots = []
    _logger.info("Computing for total %d weeks" % totalWeeks)
    for i in range(totalWeeks):
        nDays = i * 7
        monday = nextMonday + datetime.timedelta(nDays)

        # AWS don't care about holidays.
        if monday in holidays_:
            _logger.warn("This date %s is holiday" % monday)
            continue

        nSlots = 3
        if monday in upcoming_aws_slots_:
            # Check how many of these dates have been taken.
            _logger.info('Date %s is taken ' % monday)
            nSlots -= len(upcoming_aws_slots_[monday])

        # For each Monday, we have 3 AWS - (assigned on upcoming_aws_slots_)
        for j in range(nSlots):
            dateSlot = '%s,%d' % (monday, j)
            g_.add_node(dateSlot, date=monday, pos=(5, 10 * (3 * i + j)))
            g_.add_edge(dateSlot, 'sink', capacity=1, weight=0)
            slots.append(dateSlot)

    # Now for each student, add potential edges.
    idealGap = 7 * 60

    # Keep edges from freshers to dates here. We allow maximum of 2 out of 3
    # slots to be taken by freshers (maximum ).
    freshersDate = defaultdict(list)
    for speaker in speakers_:
        preferences = aws_scheduling_requests_.get(speaker, {})
        if preferences:
            _logger.info("%s has preferences %s " % (speaker, preferences))

        if speaker not in g_.nodes():
            _logger.info('Nothing for user %s' % speaker)
            continue

        prevAWSDate = g_.node[speaker]['last_date']
        for slot in slots:
            date = g_.node[slot]['date']
            weight = computeCost(speaker, date, prevAWSDate)
            if weight:
                # If the speaker is fresher, do not draw edges to all three
                # slots. Draw just one but make sure that they get this slot. We
                # reduce the cost to almost zero.
                if speaker in freshers:
                    # Let two freshers take maximum of two slots on same day.
                    # The weight should be low but not lower than user
                    # preference.
                    if freshersDate.get(speaker, []).count(date) < 2:
                        addEdge(speaker, slot, 1, 5)
                        # This date is taken by this fresher.
                        freshersDate[speaker].append(date)
                else:
                    addEdge(speaker, slot, 1, weight)

                # Honour user preferences..
                if preferences:
                    first = preferences.get('first_preference', None)
                    second = preferences.get('second_preference', None)
                    if first:
                        ndays = diffInDays(date, first, True)
                        if ndays <= 14:
                            _logger.info('Using first preference for %s' %
                                         speaker)
                            addEdge(speaker, slot, 1, 0 + ndays / 7)
                    if second:
                        ndays = diffInDays(date, second, True)
                        if ndays <= 14:
                            _logger.info('Using second preference for %s' %
                                         speaker)
                            addEdge(speaker, slot, 1, 2 + ndays / 7)

    _logger.info('Constructed flow graph')
Esempio n. 6
0
def construct_flow_graph(seed=0):
    """This is the most critical section of this task. It is usually good if
    flow graph is constructed to honor policy as much as possible rather than
    fixing the solution later.

    One important scheduling aim is to minimize number of freshers on the same
    day. Ideally no more than 1 freshers should be allowed on same day. This can
    be achieved by modifying the solution later : swapping freshers with
    experienced speaker from other days. We avoid that by drawing two edges
    from freshers to a 'date' i.e. maximum of 2 slots can be filled by freshers.
    For others we let them fill all three slots.

    4 slots every monday and all belong to one specialization.
    """

    g_.clear()

    g_.add_node('source', pos=(0, 0))
    g_.add_node('sink', pos=(10, 10))

    # Compute totalWeeks of schedule starting today.
    totalWeeks = 32
    d = datetime.date.today()
    while d.weekday() != 0:  # 0 is monday
        d += datetime.timedelta(days=1)
    nextMonday = d
    slots = []

    weeks = [afterNDays(nextMonday, 7 * i) for i in range(totalWeeks)]

    specializations = chooseSpecialization(totalWeeks, seed)

    # Ignore the already filled upcoming AWS slots.
    freq = Counter(speakersSpecialization_.values())
    for monday in sorted(upcoming_aws_slots_):
        speakers = upcoming_aws_slots_[monday]
        for speaker in speakers:
            specialization = speakersSpecialization_.get(
                speaker, 'UNSPECIFIED')
            freq[specialization] = max(0, freq[specialization] - 1)

    # Update the frequencies.
    for k in freq:
        specializationFreqs_[k] = 1.0 * freq[k] / sum(freq.values())

    # Collect all the valid slots with specialization in a list.
    validSlots = []
    for specForWeek, monday in zip(specializations, weeks):
        # AWS don't care about holidays.
        if monday in holidays_:
            _logger.warn("This date %s is holiday" % monday)
            continue

        nSlots = 3
        if monday in upcoming_aws_slots_:
            # Check how many of these dates have been taken.
            _logger.info('Date %s is taken ' % monday)
            nSlots -= len(upcoming_aws_slots_[monday])
            for i in range(nSlots):
                validSlots.append(('%s,%s' % (monday, i), monday, specForWeek))
        else:
            validSlots += [('%s,%s' % (monday, i), monday, specForWeek)
                           for i in range(nSlots)]

    # Keep edges from freshers to dates here. We allow maximum of 2 out of 3
    # slots to be taken by freshers (maximum ).
    missed = construct_graph(validSlots)
    return missed
Esempio n. 7
0
def construct_graph(validSlots):

    lastDate = None
    for i, speaker in enumerate(speakers_):
        lastDate = get_prev_aws_date(speaker)
        # If a speaker has a lastDate either because he has given AWS in the
        # past or becuase she is fresher. Create an edge.
        if lastDate is not None:
            g_.add_node(speaker, last_date=lastDate, pos=(1, 3 * i))
            g_.add_edge('source', speaker, capacity=1, weight=0)

    for sid, monday, specForWeek in validSlots:
        # For each Monday, we have 3 AWS - (assigned on upcoming_aws_slots_)
        # For each week select a specialization.
        _logger.info("++ Specialization for this week is %s" % specForWeek)
        g_.add_node(sid, date=monday, specialization=specForWeek)
        g_.add_edge(sid, 'sink', capacity=1, weight=0)

    # Now for each student, add potential edges.
    idealGap = 357

    freshersDate = defaultdict(list)
    for speaker in speakers_:
        speakerSpecialization = speakersSpecialization_.get(speaker, '')
        preferences = aws_scheduling_requests_.get(speaker, {})

        if preferences:
            _logger.info("%s has preferences %s " % (speaker, preferences))

        if speaker not in g_.nodes():
            _logger.info('Nothing for user %s' % speaker)
            continue

        prevAWSDate = g_.node[speaker]['last_date']
        for slot, monday, speci in validSlots:
            # If this slot does not belong to some specialization then ignore
            # it.
            if g_.node[slot]['specialization'] != speakerSpecialization:
                continue

            date = g_.node[slot]['date']
            weight = computeCost(speaker, date, prevAWSDate)
            if weight is None:
                continue

            # If the speaker is fresher, do not draw edges to all three
            # slots. Draw just one but make sure that they get this slot. We
            # reduce the cost to almost zero.
            if speaker in freshers_:
                # Let two freshers take maximum of two slots on same day.
                # The weight should be low but not lower than user
                # preference.
                if freshersDate.get(speaker, []).count(date) < 2:
                    addEdge(speaker, slot, 1, 5)
                    # This date is taken by this fresher.
                    freshersDate[speaker].append(date)
            else:
                addEdge(speaker, slot, 1, weight)

            # Honour user preferences..
            if preferences:
                first = preferences.get('first_preference', None)
                second = preferences.get('second_preference', None)
                if first:
                    ndays = diffInDays(date, first, True)
                    if ndays <= 14:
                        _logger.debug('Using first preference for %s' %
                                      speaker)
                        addEdge(speaker, slot, 1, 0 + ndays / 7)
                if second:
                    ndays = diffInDays(date, second, True)
                    if ndays <= 14:
                        _logger.info('Using second preference for %s' %
                                     speaker)
                        addEdge(speaker, slot, 1, 2 + ndays / 7)

    # Each slot node must have at least 3 nodes.
    missedSlots = []
    for slot, monday, speci in validSlots:
        inDegree = g_.in_degree(slot)
        inedges = g_.predecessors(slot)
        if inDegree < 1:
            _logger.warn("slot %s [%s] have no options" % (slot, speci))
            missedSlots.append(slot)

    _logger.info('Constructed flow graph')
    return missedSlots
Esempio n. 8
0
def get_prev_aws_date(speaker):

    lastDate = None

    if speakersSpecialization_.get(speaker, 'UNSPECIFIED') == 'UNSPECIFIED':
        _logger.warning("Could not find specialization for %s" % speaker)

    # Last entry is most recent
    if speaker not in aws_.keys():
        # We are here because this speaker has not given any AWS yet.
        freshers_.add(speaker)

        # If this user has PHD/POSTDOC, or INTPHD title. We create  a dummy
        # last date to bring her into potential speakers.
        # First make sure, I have their date of joining. Otherwise I
        # can't continue. For MSc By Research/INTPHD assign their first AWS
        # after 18 months. For PHD and POSTDOC, it should be after 12 months.
        if speakers_[speaker]['title'] == 'INTPHD':
            # InPhd should get their first AWS after 15 months of
            # joining.
            _logger.info('%s = INTPHD with 0 AWS so far' % speaker)
            joinDate = speakers_[speaker]['joined_on']
            if not joinDate:
                _logger.warn("Could not find joining date")
            else:
                lastDate = monthdelta(joinDate, +6)

        if speakers_[speaker]['title'] == 'MSC':
            # MSc should get their first AWS after 18 months of
            # joining. Same as INTPHD
            _logger.info('%s = MSC BY RESEARCH with 0 AWS so far' % speaker)
            joinDate = speakers_[speaker]['joined_on']
            if not joinDate:
                _logger.warn("Could not find joining date")
            else:
                lastDate = monthdelta(joinDate, +6)

        elif speakers_[speaker]['title'] in ['PHD', 'POSTDOC']:
            joinDate = speakers_[speaker]['joined_on']
            _logger.info('%s PHD/POSTDOC with 0 AWS so far' % speaker)
            if not joinDate:
                _logger.warn("Could not find joining date")
            else:
                try:
                    # if datetime.
                    lastDate = joinDate.date()
                except Exception as e:
                    # Else its date
                    lastDate = joinDate
    else:
        # We are here because this speaker has given AWS before
        # If this speaker is already on upcoming AWS list, ignore it.
        if speaker in upcoming_aws_:
            _logger.info('Speaker %s is already scheduled on %s' %
                         (speaker, upcoming_aws_[speaker]))
            return None
        # If this speakers is MSC by research and has given AWS before, she/he
        # need not give another.
        elif speakers_[speaker]['title'] == 'MSC':
            _logger.info('%s is MSC and has given AWS in the past' % speaker)
            return None
        else:
            lastDate = aws_[speaker][-1]['date']
    return lastDate