Beispiel #1
0
def pair_round():
    """
    Pair the next round of debate.
    This function will do the following:
        1) Verify we can pair the next round
        2) Check that we have scratched all judges from
           teams of the same school, and if not add these
           scratches
        3) Record no-show teams
        4) Setup the list of teams by either seed or speaks
        5) Calculate byes
        6) Calculate pull ups based on byes
        7) Pass in evened brackets to the perfect pairing algorithm
        8) Assign rooms to pairings

    Judges are added later.

    pairings are computed in the following format: [gov,opp,judge,room]
    and then saved immediately into the database

    FIXME: Allow for good rollback behavior
    """
    current_round = TabSettings.get('cur_round')
    validate_round_data(current_round)

    # Set released to false so we don't show pairings
    TabSettings.set('pairing_released', 0)

    # Need a reproduceable random pairing order
    random.seed(0xBEEF)

    # add scratches for teams/judges from the same school
    # NOTE that this only happens if they haven't already been added
    add_scratches_for_school_affil()

    list_of_teams = [None]*current_round
    all_pull_ups = []

    # Record no-shows
    forfeit_teams = list(Team.objects.filter(checked_in=False))
    for t in forfeit_teams:
        lenient_late = TabSettings.get('lenient_late', 0) >= current_round
        n = NoShow(no_show_team = t, round_number = current_round, lenient_late = lenient_late)
        n.save()

    # If it is the first round, pair by *seed*
    if current_round == 1:
        list_of_teams = list(Team.objects.filter(checked_in=True))

        # If there are an odd number of teams, give a random team the bye
        if len(list_of_teams) % 2 == 1:
            if TabSettings.get('fair_bye', 1) == 0:
                print "Bye: using only unseeded teams"
                possible_teams = [t for t in list_of_teams if t.seed < Team.HALF_SEED]
            else:
                print "Bye: using all teams"
                possible_teams = list_of_teams
            bye_team = random.choice(possible_teams)
            b = Bye(bye_team=bye_team, round_number=current_round)
            b.save()
            list_of_teams.remove(b.bye_team)

        # Sort the teams by seed. We must randomize beforehand so that similarly
        # seeded teams are paired randomly.
        random.shuffle(list_of_teams)
        list_of_teams = sorted(list_of_teams, key=lambda team: team.seed, reverse = True)
    # Otherwise, pair by *speaks*
    else:
        # Bucket all the teams into brackets
        # NOTE: We do not bucket teams that have only won by
        #       forfeit/bye/lenient_late in every round because they have no speaks
        middle_of_bracket = middle_of_bracket_teams()
        all_checked_in_teams = Team.objects.filter(checked_in=True)
        normal_pairing_teams = all_checked_in_teams.exclude(pk__in=map(lambda t: t.id, middle_of_bracket))

        team_buckets = [ (tot_wins(team), team) for team in normal_pairing_teams ]
        list_of_teams = [rank_teams_except_record([team
                                                  for (w,team) in team_buckets
                                                  if w == i])
                         for i in range(current_round)]

        for team in middle_of_bracket:
            wins = tot_wins(team)
            print("Pairing %s into the middle of the %s-win bracket" % (team, wins))
            bracket_size = len(list_of_teams[wins])
            bracket_middle = bracket_size / 2
            list_of_teams[wins].insert(bracket_middle, team)

        print "these are the teams before pullups"
        print pprint.pprint(list_of_teams)

        # Correct for brackets with odd numbers of teams
        #  1) If we are in the bottom bracket, give someone a bye
        #  2) If we are in 1-up bracket and there are no all down
        #     teams, give someone a bye
        #  FIXME: Do we need to do special logic for smaller brackets? - (julia) I need to make the logic more general to deal
        # with if there are no teams in the all down or up one bracket. See Issue 4
        #  3) Otherwise, find a pull up from the next bracket
        for bracket in reversed(range(current_round)):
            if len(list_of_teams[bracket]) % 2 != 0:
                # If there are no teams all down, give the bye to a one down team.
                if bracket == 0:
                    byeint = len(list_of_teams[bracket]) - 1
                    b = Bye(bye_team = list_of_teams[bracket][byeint],
                            round_number = current_round)
                    b.save()
                    list_of_teams[bracket].remove(list_of_teams[bracket][byeint])
                elif bracket == 1 and len(list_of_teams[0]) == 0: #in 1 up and no all down teams
                    found_bye = False
                    for byeint in range(len(list_of_teams[1])-1, -1, -1):
                        if had_bye(list_of_teams[1][byeint]):
                            pass
                        elif found_bye == False:
                            b = Bye(bye_team = list_of_teams[1][byeint], round_number = current_round)
                            b.save()
                            list_of_teams[1].remove(list_of_teams[1][byeint])
                            found_bye = True
                    if found_bye == False:
                        raise errors.NotEnoughTeamsError()
                else:
                    pull_up = None
                    # i is the last team in the bracket below
                    i = len(list_of_teams[bracket-1]) - 1
                    pullup_rounds = Round.objects.exclude(pullup=Round.NONE)
                    teams_been_pulled_up = [r.gov_team for r in pullup_rounds if r.pullup == Round.GOV]
                    teams_been_pulled_up.extend([r.opp_team for r in pullup_rounds if r.pullup == Round.OPP])

                    # try to pull-up the lowest-ranked team that hasn't been
                    # pulled-up. Fall-back to the lowest-ranked team if all have
                    # been pulled-up
                    not_pulled_up_teams = filter(
                            lambda t: t not in teams_been_pulled_up,
                            list_of_teams[bracket-1])
                    if len(not_pulled_up_teams) > 0:
                        pull_up = not_pulled_up_teams[-1]
                    else:
                        pull_up = list_of_teams[bracket-1][-1]

                    all_pull_ups.append(pull_up)
                    list_of_teams[bracket].append(pull_up)
                    list_of_teams[bracket-1].remove(pull_up)

                    # after adding pull-up to new bracket and deleting from old, sort again by speaks making sure to leave any first
                    # round bye in the correct spot
                    removed_teams = []
                    for t in list(Team.objects.filter(checked_in=True)):
                        # They have all wins and they haven't forfeited so they need to get paired in
                        if t in middle_of_bracket and tot_wins(t) == bracket:
                            removed_teams += [t]
                            list_of_teams[bracket].remove(t)
                    list_of_teams[bracket] = rank_teams_except_record(list_of_teams[bracket])
                    print "list of teams in " + str(bracket) + " except removed"
                    print list_of_teams[bracket]
                    for t in removed_teams:
                        list_of_teams[bracket].insert(len(list_of_teams[bracket])/2,t)

    print "these are the teams after pullups"
    print pprint.pprint(list_of_teams)
    if current_round > 1:
        for i in range(len(list_of_teams)):
            print "Bracket %i has %i teams" % (i, len(list_of_teams[i]))

    # Pass in the prepared nodes to the perfect pairing logic
    # to get a pairing for the round
    pairings = []
    for bracket in range(current_round):
        if current_round == 1:
            temp = pairing_alg.perfect_pairing(list_of_teams)
        else:
            temp = pairing_alg.perfect_pairing(list_of_teams[bracket])
            print "Pairing round %i of size %i" % (bracket,len(temp))
        for pair in temp:
            pairings.append([pair[0],pair[1],[None],[None]])

    # FIXME: WHY DO WE RANDOMIZE THIS - we want the order of which fullseeded teams get the best judge to be random.
    # We should possibly also sort on the weakest team first? I.e. a fullseed/halfseed should get a better judge than a
    # fullseed/freeseed, etc. - Julia to fix. Issue 6.
    # should randomize first
    if current_round == 1:
        random.shuffle(pairings, random=random.random)
        pairings = sorted(pairings, key=lambda team: highest_seed(team[0], team[1]), reverse = True)
    # sort with pairing with highest ranked team first
    else:
        sorted_teams = rank_teams()
        print sorted_teams
        print "pairings"
        print pairings
        pairings = sorted(pairings, key=lambda team: min(sorted_teams.index(team[0]), sorted_teams.index(team[1])))

    # Assign rooms (does this need to be random? maybe bad to have top ranked teams/judges in top rooms?)
    rooms = Room.objects.all()
    rooms = sorted(rooms, key=lambda r: r.rank, reverse = True)

    for i in range(len(pairings)):
        pairings[i][3] = rooms[i]

    # Enter into database
    for p in pairings:
        if isinstance(p[2], Judge):
            r = Round(round_number = current_round,
                      gov_team = p[0],
                      opp_team = p[1],
                      judge = p[2],
                      room = p[3])
        else:
            r = Round(round_number = current_round,
                      gov_team = p[0],
                      opp_team = p[1],
                      room = p[3])
        if p[0] in all_pull_ups:
            r.pullup = Round.GOV
        elif p[1] in all_pull_ups:
            r.pullup = Round.OPP
        r.save()
Beispiel #2
0
def pair_round():
    """
    Pair the next round of debate.
    This function will do the following:
        1) Check that we can pair the round
        2) Check that we have scratches all judges from
           teams of the same school, and if not add these
           scratches
        3) Record no-show teams
        4) Setup the list of teams by either seed or speaks
        5) Calculate byes
        6) Calculate pull ups based on byes
        7) Pass in evened brackets to the perfect pairing algorithm
        8) Assign judges to pairings
        9) Assign rooms to pairings

    pairings are computed in the following format: [gov,opp,judge,room]
    and then directly put into the database
    FIXME: Allow for good rollback behavior
    """
    current_round = TabSettings.objects.get(key="cur_round").value
    try:
        ready_to_pair(current_round)
    except errors.NotEnoughJudgesError:
        raise errors.NotEnoughJudgesError()
    except errors.NotEnoughRoomsError:
        raise errors.NotEnoughRoomsError()
    except errors.PrevRoundNotEnteredError:
        raise errors.PrevRoundNotEnteredError

    # For testing purposes
    random.seed(0xBEEF)

    # add scratches for teams/judges from the same school
    # NOTE that this only happens if they haven't already been added
    add_scratches_for_school_affil()

    list_of_teams = [None] * current_round
    all_pull_ups = []

    # Record no-shows
    forfeit_teams = list(Team.objects.filter(checked_in=False))
    for t in forfeit_teams:
        n = NoShow(no_show_team=t, round_number=current_round)
        n.save()

    # If it is the first round, pair by *seed*
    if current_round == 1:
        list_of_teams = list(Team.objects.filter(checked_in=True))

        # If there are an odd number of teams, give a random team the bye
        if len(list_of_teams) % 2 == 1:
            b = Bye(bye_team=list_of_teams[random.randint(
                0,
                len(list_of_teams) - 1)],
                    round_number=current_round)
            b.save()
            list_of_teams.remove(b.bye_team)

        # Sort the teams by seed. We must randomize beforehand so that similarly
        # seeded teams are paired randomly.
        random.shuffle(list_of_teams)
        list_of_teams = sorted(list_of_teams,
                               key=lambda team: team.seed,
                               reverse=True)

        #pairings = [None]* Team.objects.count()/2
    # Otherwise, pair by *speaks*
    else:
        bye_teams = [bye.bye_team for bye in Bye.objects.all()]
        # For each time that a team has won by forfeit, add them
        # to the list of bye_teams
        bye_teams = bye_teams + team_wins_by_forfeit()
        # FIXME (jolynch): Why is this random thing here? - (julia) If there are multiple teams that have had the bye/won by forfeit,
        #we want to order that they are inserted into the middle of the bracket to be random.  I need to change the code below so
        #that this is actually true/used - See issue 3
        random.shuffle(bye_teams, random=random.random)

        # Bucket all the teams into brackets
        # NOTE: We do not bucket teams that have only won by
        #       forfeit/bye in every round because they have no speaks
        all_checked_in_teams = Team.objects.filter(checked_in=True)
        team_buckets = [(tot_wins(team), team) for team in all_checked_in_teams
                        if current_round - bye_teams.count(team) != 1]
        list_of_teams = [
            rank_teams_except_record(
                [team for (w, team) in team_buckets if w == i])
            for i in range(current_round)
        ]

        # Take care of teams that only have forfeits/byes
        # FIXME (julia): This should just look at the bye teams. No need to look at all teams, plus looking only at bye teams will
        #insert them in a random order. See issue 3
        if len(bye_teams) != 0:
            for t in list(Team.objects.filter(checked_in=True)):
                # pair into the middle
                if current_round - bye_teams.count(t) == 1:
                    print t
                    list_of_teams[current_round - 1].insert(
                        int(float(len(list_of_teams[tot_wins(t)])) / 2.0), t)
        print "these are the teams before pullups"
        print pprint.pprint(list_of_teams)

        # Correct for brackets with odd numbers of teams
        #  1) If we are in the bottom bracket, give someone a bye
        #  2) If we are in 1-up bracket and there are no all down
        #     teams, give someone a bye
        #  FIXME: Do we need to do special logic for smaller brackets? - (julia) I need to make the logic more general to deal
        # with if there are no teams in the all down or up one bracket. See Issue 4
        #  3) Otherwise, find a pull up from the next bracket
        for bracket in reversed(range(current_round)):
            if len(list_of_teams[bracket]) % 2 != 0:
                #print "need pull-up"
                # If there are no teams all down, give the bye to a one down team.
                if bracket == 0:
                    byeint = len(list_of_teams[bracket]) - 1
                    b = Bye(bye_team=list_of_teams[bracket][byeint],
                            round_number=current_round)
                    b.save()
                    list_of_teams[bracket].remove(
                        list_of_teams[bracket][byeint])
                elif bracket == 1 and len(
                        list_of_teams[0]) == 0:  #in 1 up and no all down teams
                    found_bye = False
                    for byeint in range(len(list_of_teams[1]) - 1, -1, -1):
                        if had_bye(list_of_teams[1][byeint]):
                            pass
                        elif found_bye == False:
                            b = Bye(bye_team=list_of_teams[1][byeint],
                                    round_number=current_round)
                            b.save()
                            list_of_teams[1].remove(list_of_teams[1][byeint])
                            found_bye = True
                    if found_bye == False:
                        raise errors.NotEnoughTeamsError()
                else:
                    pull_up = None
                    # FIXME (jolynch): Try to use descriptive variable names. (julia) - I'll fix this.
                    # instead of commenting

                    # i is the last team in the bracket below
                    i = len(list_of_teams[bracket - 1]) - 1
                    pullup_rounds = Round.objects.exclude(pullup=Round.NONE)
                    teams_been_pulled_up = [
                        r.gov_team for r in pullup_rounds
                        if r.pullup == Round.GOV
                    ]
                    teams_been_pulled_up.extend([
                        r.opp_team for r in pullup_rounds
                        if r.pullup == Round.OPP
                    ])
                    #find the lowest team in bracket below that can be pulled up
                    while pull_up == None:
                        if list_of_teams[bracket -
                                         1][i] not in teams_been_pulled_up:
                            pull_up = list_of_teams[bracket - 1][i]
                            all_pull_ups.append(pull_up)
                            list_of_teams[bracket].append(pull_up)
                            list_of_teams[bracket - 1].remove(pull_up)
                            #after adding pull-up to new bracket and deleting from old, sort again by speaks making sure to leave any first
                            #round bye in the correct spot
                            removed_teams = []
                            for t in list(
                                    Team.objects.filter(checked_in=True)):
                                #They have all wins and they haven't forfeited so they need to get paired in
                                if current_round - bye_teams.count(
                                        t) == 1 and tot_wins(t) == bracket:
                                    removed_teams += [t]
                                    list_of_teams[bracket].remove(t)
                            list_of_teams[bracket] = rank_teams_except_record(
                                list_of_teams[bracket])
                            print "list of teams in " + str(
                                bracket) + " except removed"
                            print list_of_teams[bracket]
                            for t in removed_teams:
                                list_of_teams[bracket].insert(
                                    len(list_of_teams[bracket]) / 2, t)
                        else:
                            i -= 1
    print "these are the teams after pullups"
    print pprint.pprint(list_of_teams)
    if current_round > 1:
        for i in range(len(list_of_teams)):
            print "Bracket %i has %i teams" % (i, len(list_of_teams[i]))

    # Pass in the prepared nodes to the perfect pairing logic
    # to get a pairing for the round
    pairings = []
    for bracket in range(current_round):
        if current_round == 1:
            temp = pairing_alg.perfect_pairing(list_of_teams)
        else:
            temp = pairing_alg.perfect_pairing(list_of_teams[bracket])
            print "Pairing round %i of size %i" % (bracket, len(temp))
        for pair in temp:
            pairings.append([pair[0], pair[1], [None], [None]])

    # FIXME: WHY DO WE RANDOMIZE THIS - we want the order of which fullseeded teams get the best judge to be random.
    # We should possibly also sort on the weakest team first? I.e. a fullseed/halfseed should get a better judge than a
    # fullseed/freeseed, etc. - Julia to fix. Issue 6.
    # should randomize first
    if current_round == 1:
        random.shuffle(pairings, random=random.random)
        pairings = sorted(pairings,
                          key=lambda team: highest_seed(team[0], team[1]),
                          reverse=True)
    # sort with pairing with highest ranked team first
    else:
        sorted_teams = rank_teams()
        print sorted_teams
        print "pairings"
        print pairings
        pairings = sorted(pairings,
                          key=lambda team: min(sorted_teams.index(team[0]),
                                               sorted_teams.index(team[1])))

    # Assign rooms (does this need to be random? maybe bad to have top ranked teams/judges in top rooms?)
    rooms = Room.objects.all()
    rooms = sorted(rooms, key=lambda r: r.rank, reverse=True)

    for i in range(len(pairings)):
        pairings[i][3] = rooms[i]

    # Enter into database
    for p in pairings:
        if isinstance(p[2], Judge):
            r = Round(round_number=current_round,
                      gov_team=p[0],
                      opp_team=p[1],
                      judge=p[2],
                      room=p[3])
        else:
            r = Round(round_number=current_round,
                      gov_team=p[0],
                      opp_team=p[1],
                      room=p[3])
        if p[0] in all_pull_ups:
            r.pullup = Round.GOV
        elif p[1] in all_pull_ups:
            r.pullup = Round.OPP
        r.save()