From f98b3b70b254987c4eaca2f5a88bed57caa7efb2 Mon Sep 17 00:00:00 2001 From: dave Date: Sun, 29 Mar 2009 04:31:16 +0000 Subject: [PATCH] New judge scheduler, deals with multiple rounds of judging. Not quite ready for general consumption (there is no way to turn OFF the 2nd round yet), and the 2nd round scheduler needs a bit of work. --- admin/judges_sa.php | 892 +++++++++++++++++++++++++++++--------------- 1 file changed, 599 insertions(+), 293 deletions(-) diff --git a/admin/judges_sa.php b/admin/judges_sa.php index 1a61987..e835403 100644 --- a/admin/judges_sa.php +++ b/admin/judges_sa.php @@ -34,40 +34,6 @@ if($_SERVER['SERVER_ADDR']) { exit; } -/* -send_header("Judging teams automatic scheduler"); - - -echo i18n("The scheduler is running. Depending on the effort it may take -several minutes to complete. It will generate a lot of output on this page, -most of it is just information about what the scheduler is doing. You can copy -this page and save it as a text file to have a record of what the scheduler -did. (And this output helps us to debug problems with it if you encounter -something that clearly isn't correct, just send us this output)"); -echo "
"; -echo "
"; -echo i18n("When complete, a green bar will appear at the BOTTOM of this page -saying that everything has completed successfully. When complete, you can use -the following links to manage the Judging Teams and the Judges assigned to them -(clicking on these links now will stop the scheduler)."); -echo "
"; -echo "
"; -echo "".i18n("Manage Judge Teams").""; -echo "
"; -echo "".i18n("Manage Judge Members").""; -echo "
"; -echo "
"; -echo i18n("If you get an error like: \"Fatal error: Maximum execution time of -30 seconds exceeded...\" you will need to talk to your system admin and have -them adjust the \"max_execution_time\" variable in the \"php.ini\" file from -30(seconds) to something larger, like 900 (15 minutes). And then have them -restart the webserver for the change to take effect"); -echo "
"; -echo "
"; - -*/ - - //function TRACE() { } //function TRACE_R() { } function TRACE($str) { print($str); } @@ -131,10 +97,12 @@ function judges_cost_function($annealer, $bucket_id, $ids) // TRACE_R($ids); $cost = 0; - $have_chair = 0; + $have_chair = false; + $have_div2 = false; if($bucket_id == 0) { - /* This is the placeholder */ + /* This is the placeholder for all judges, there's a slight + * cost for not using a judge */ $cost = count($ids) * 5; // TRACE("Extra judge team cost=$cost\n"); return $cost; @@ -180,7 +148,7 @@ function judges_cost_function($annealer, $bucket_id, $ids) $cost += 2 * $dpref; /* See if the judge is willing to chair a team */ - if($j['willing_chair'] == 'yes') $have_chair = 1; + if($j['willing_chair'] == 'yes') $have_chair = true; /* For each lang the team needs that the judge doesn't have, * increase the cost */ @@ -188,10 +156,19 @@ function judges_cost_function($annealer, $bucket_id, $ids) $l = $t['langs'][$y]; if(!in_array($l, $j['languages'])) $cost += 25; } + + /* If divisional round2 is enabled, make sure there is a judge + * on the team for round2 */ + if($j['available_for_divisional2'] == true) $have_div2 = true; } /* Huge penalty for a team without a willing chair, but only if the min judges per team >1 */ if(!$have_chair && $config['min_judges_per_team']>1) $cost += 40; + + /* Huge penalty for not having a round2 personal on the + * team */ + if($have_div2 == false) + $cost += 40; // TRACE("Team $bucket_id, cost is $cost\n"); @@ -238,6 +215,96 @@ function jdiv_compute_cost($annealer, $bucket_id, $ids) return $cost; } +/* Returns true if a judge time preference indicates they are available for the + * specified round. Always returns true if judge time availablility selection + * is off */ +function judge_available_for_round($j, $r) +{ + global $config; + if($config['judges_availability_enable'] == 'no') return true; + + foreach($j['availability'] as $a) { + if($a['start'] <= $r['starttime'] + && $a['end'] >= $r['endtime'] + && $a['date'] == $r['date'] ) { + return true; + } + } + return false; +} + +function judge_mark_for_round($j, $r) +{ + /* The judge has been assigned to round $r, modify their available to + * exclude any time that falls within this time + * TODO: modify the DB to store date/times in timestamps, so we don't + * have to deal with dates separately. */ + global $config; + global $judges; + if($config['judges_availability_enable'] == 'no') return true; + + /* Grab a pointer to the real judge, because we want to + * modify it, not a copy of it */ + $ju =& $judges[$j['id']]; + + foreach($ju['availability'] as $key=>&$a) { + if($r['starttime'] >= $a['start'] && $r['starttime'] <= $a['end']) { + /* Round starts in the middle of this availablity slot + * modify this availabilty so it doesn't overlap */ + /* This may cause $a['start'] == $a['end'], that's ok */ + $a['end'] = $r['starttime']; + TRACE("adjust starttime\n"); + } + + if($r['endtime'] >= $a['start'] && $r['endtime'] <= $a['end']) { + /* Round ends in the middle of this availablity slot + * modify this availabilty so it doesn't overlap */ + /* This may cause $a['start'] == $a['end'], that's ok */ + $a['start'] = $r['endtime']; + TRACE("adjust starttime\n"); + } + + if($a['start'] >= $a['end']) { + /* Delete the whole round */ + unset($ju['availability'][$key]); + } + } + + print_r($ju['availability']); + +} + +/* UNUSED: should be moved to the timeslot manager to ensure rounds + * don't overlap. */ +function rounds_overlap($r1, $r2) { + $s1 = strtotime("{$r1['date']} {$r1['starttime']}"); + $e1 = strtotime("{$r1['date']} {$r1['endtime']}"); + $s2 = strtotime("{$r1['date']} {$r2['starttime']}"); + $e2 = strtotime("{$r1['date']} {$r2['endtime']}"); + + if($s1 <= $s2 && $e1 > $s1) return true; + if($s1 > $s2 && $s1 < $e2) return true; + return false; +} + +/* Print a judge */ +function pr_judge(&$jt, $jid) +{ + global $judges; + $j =& $judges[$jid]; + print(" - {$j['name']} (".join(' ', $j['languages']).')'); + print("("); + foreach($jt['cats'] as $c) + print("c{$c}={$j['cat_prefs'][$c]} "); + foreach($jt['divs'] as $d) + print("d{$d}={$j['div_prefs'][$d]} "); + + print(")"); + if($j['willing_chair'] == 'yes') print(" (chair) "); + + print("\n"); +} + set_status("Loading Data From Database..."); TRACE("\n\n"); @@ -267,6 +334,27 @@ while($r=mysql_fetch_object($q)) { TRACE(" {$r->lang} - {$r->langname}\n"); } +TRACE("Loading Judging Round time data...\n"); +$round_divisional1 = NULL; +$round_divisional2 = NULL; +$round_special_awards = array(); +$round = array(); +$q = mysql_query("SELECT * FROM judges_timeslots WHERE round_id='0' AND `year`='{$config['FAIRYEAR']}'"); +/* Loads judges_timeslots.id, .starttime, .endtime, .date, .name */ +while($r = mysql_fetch_assoc($q)) { + TRACE(" id:{$r['id']} type:{$r['type']} name:{$r['name']}\n"); + $round[] = $r; + + if($r['type'] == 'divisional1') $round_divisional1 = $r; + if($r['type'] == 'divisional2') $round_divisional2 = $r; + if($r['type'] == 'special') $round_special_awards[] = $r; +} + +if($round_divisional1 == NULL) { + echo "No divisional1 round defined! Aborting!\n"; + exit; +} + $jdiv = array(); TRACE("Loading Judging Division Configuration and Projects...\n"); $q=mysql_query("SELECT * FROM judges_jdiv"); @@ -280,8 +368,7 @@ while($r=mysql_fetch_object($q)) { } $keys = array_keys($jdiv); -for($k=0; $kid; + print(" $id"); + /* Clean out the judges_teams_link */ + mysql_query("DELETE FROM judges_teams_link WHERE judges_teams_id='$id' AND year={$config['FAIRYEAR']}"); + print mysql_error(); + /* Awards */ + mysql_query("DELETE FROM judges_teams_awards_link WHERE judges_teams_id='$id' AND year={$config['FAIRYEAR']}"); + print mysql_error(); + /* Timeslots */ + mysql_query("DELETE FROM judges_teams_timeslots_link WHERE judges_teams_id='$id' AND year={$config['FAIRYEAR']}"); + print mysql_error(); + /* Timeslots projects */ + mysql_query("DELETE FROM judges_teams_timeslots_projects_link WHERE judges_teams_id='$id' AND year={$config['FAIRYEAR']}"); + print mysql_error(); +} +echo "\n"; + +/* Finally, delete all the autocreated judges teams */ +mysql_query("DELETE FROM judges_teams WHERE autocreate_type_id=1 AND year={$config['FAIRYEAR']}"); +print mysql_error(); + +/* Also delete any judges_teams_link that link to teams that dont exist, just + * in case */ +$q=mysql_query("SELECT judges_teams_link.id, judges_teams.id AS judges_teams_id + FROM judges_teams_link + LEFT JOIN judges_teams ON judges_teams_link.judges_teams_id=judges_teams.id + WHERE judges_teams_link.year={$config['FAIRYEAR']}"); +$n=0; +while($r=mysql_fetch_object($q)) { + if(!$r->judges_teams_id) { + mysql_query("DELETE FROM judges_teams_link WHERE id='$r->id'"); + $n++; + } +} +print("Deleted $n orphaned team linkings\n"); +TRACE(" Done.\n"); + + +set_status("Loading Judges"); + +$judges = judges_load_all(); + +foreach($judges as &$j) { + if($j['judge_active'] == 'no') { + TRACE(" {$j['name']} has their judge profile deactivated, skipping.\n"); + unset($judges[$j['id']]); + continue; + } + if($j['judge_complete'] == 'no') { + TRACE(" {$j['name']} hasn't completed their judge profile, skipping.\n"); + unset($judges[$j['id']]); + continue; + } + + $q = mysql_query("SELECT users_id FROM judges_teams_link WHERE + users_id='{$j['id']}' + AND year='{$config['FAIRYEAR']}'"); + if(mysql_num_rows($q) != 0) { + TRACE(" {$j['name']} is already on a judging team, skipping.\n"); + unset($judges[$j['id']]); + continue; + } + + /* Load the judge time availability */ + $q = mysql_query("SELECT * FROM judges_availability WHERE users_id='{$j['id']}' ORDER BY `start`"); + if(mysql_num_rows($q) == 0) { + TRACE(" {$j['name']} hasn't selected any time availability, POTENTIAL BUG (they shouldn't be marked as complete).\n"); + TRACE(" Ignoring this judge.\n"); + unset($judges[$j['id']]); + continue; + } + while($r = mysql_fetch_assoc($q)) { + $j['availability'][] = $r; + } + + /* Load special award preferences */ + $q = mysql_query("SELECT award_awards.id,award_awards.name FROM + judges_specialaward_sel,award_awards + WHERE + award_awards.id=judges_specialaward_sel.award_awards_id + AND judges_specialaward_sel.users_id='{$j['id']}' + AND award_awards.year='{$config['FAIRYEAR']}'"); + echo mysql_error(); + + if($j['special_award_only'] == 'yes') { + TRACE(" {$j['name']} is a special awards only.\n"); + /* Find their special award id */ + if(mysql_num_rows($q) == 0) { + TRACE(" NO special award selected! (removing special award only request)\n"); + $j['special_award_only'] = 'no'; + } else if(mysql_num_rows($q) > 1) { + TRACE(" More than ONE special award selected (removing special award only request):\n"); + $j['special_award_only'] = 'no'; + + } + } + + $j['special_awards'] = array(); + while($r = mysql_fetch_object($q)) { + TRACE(" {$r->name}\n"); + /* Add them to the SA judge list (modify the actual list, not + * $j, which is a copy */ + $j['special_awards'][] = $r->id; + } +} + + +TRACE("Loaded ".count($judges)." judges\n"); +$jteam[0]['max_judges'] = count($judges); + + +/* Load the numbers for any user-defined judge teams that already exist, + * these numbers will be off-limits for auto-assigning numbers */ +$q = mysql_query("SELECT * FROM judges_teams WHERE year={$config['FAIRYEAR']}"); +$used_judges_teams_numbers = array(); +while($i = mysql_fetch_assoc($q)) { + $used_judges_teams_numbers[] = $i['num']; +} +echo "The following judge team numbers are already used: \n"; +print_r($used_judges_teams_numbers); + +$next_judges_teams_number_try = 1; +/* A function to get the next available number */ +function next_judges_teams_number() +{ + global $used_judges_teams_numbers; + global $next_judges_teams_number_try; + + while(1) { + if(!in_array($next_judges_teams_number_try, $used_judges_teams_numbers)) break; + + $next_judges_teams_number_try++; + } + $r = $next_judges_teams_number_try; + $next_judges_teams_number_try++; + return $r; +} + +function judge_team_create($num, $name) +{ + global $config; + $name = mysql_escape_string($name); + mysql_query("INSERT INTO judges_teams (num,name,autocreate_type_id,year) + VALUES ('$num','$name','1','{$config['FAIRYEAR']}')"); + $id = mysql_insert_id(); + return $id; +} + +function judge_team_add_judge($team_id, $users_id) +{ + global $config, $judges; + mysql_query("INSERT INTO judges_teams_link + (users_id,judges_teams_id,captain,year) + VALUES ('$users_id','$team_id','{$judges[$users_id]['willing_chair']}', + '{$config['FAIRYEAR']}')"); +} + +/**************************************************************************** + * Round 1 Divisional Scheduling + * - Compute required divisional judge teams + * - Delete existing ones + * - Anneal Projects to Teams + * - Anneal Judtes to Projects + * + ***************************************************************************/ + set_status("Computing required judging teams"); TRACE(" Each judging team may judge {$config['max_projects_per_team']} projects\n"); TRACE(" Each project must be judged {$config['times_judged']} times\n"); $keys = array_keys($jdiv); -for($k=0; $kbucket[$x]; $jteam[$jteam_id]['sub'] = $x; $jteam[$jteam_id]['jdiv_id'] = $jdiv_id; @@ -370,8 +628,7 @@ for($k=0; $kid; - print(" $id"); - /* Clean out the judges_teams_link */ - mysql_query("DELETE FROM judges_teams_link WHERE judges_teams_id='$id' AND year={$config['FAIRYEAR']}"); - print mysql_error(); - /* Awards */ - mysql_query("DELETE FROM judges_teams_awards_link WHERE judges_teams_id='$id' AND year={$config['FAIRYEAR']}"); - print mysql_error(); - /* Timeslots */ - mysql_query("DELETE FROM judges_teams_timeslots_link WHERE judges_teams_id='$id' AND year={$config['FAIRYEAR']}"); - print mysql_error(); - /* Timeslots projects */ - mysql_query("DELETE FROM judges_teams_timeslots_projects_link WHERE judges_teams_id='$id' AND year={$config['FAIRYEAR']}"); - print mysql_error(); -} -/* Finally, delete all the autocreated judges teams */ -mysql_query("DELETE FROM judges_teams WHERE autocreate_type_id=1 AND year={$config['FAIRYEAR']}"); -print mysql_error(); - -/* Also delete any judges_teams_link that link to teams that dont exist */ -$q=mysql_query("SELECT judges_teams_link.id, judges_teams.id AS judges_teams_id FROM judges_teams_link LEFT JOIN judges_teams ON judges_teams_link.judges_teams_id=judges_teams.id WHERE judges_teams_link.year={$config['FAIRYEAR']}"); -$n=0; -while($r=mysql_fetch_object($q)) { - if(!$r->judges_teams_id) { - mysql_query("DELETE FROM judges_teams_link WHERE id='$r->id'"); - $n++; - } -} -print("Deleted $n orphaned team linkings\n"); -TRACE(" Done.\n"); - -set_status("Loading Judges"); - -$judges = judges_load_all(); -$sa_judges = array(); - +TRACE("Finding judges available for round1 divisional\n"); +$div1_judge_ids = array(); foreach($judges as $j) { - $q = mysql_query("SELECT users_id FROM judges_teams_link WHERE - users_id='{$j['id']}' - AND year='{$config['FAIRYEAR']}'"); - if(mysql_num_rows($q) != 0) { - TRACE(" {$j['name']} is already on a judging team, skipping.\n"); - unset($judges[$j['id']]); - continue; - } + if(judge_available_for_round($j, $round_divisional1) == false) continue; + if($j['special_award_only'] == 'yes') continue; - if($j['special_award_only'] == 'yes') { - TRACE("Judge {$j['name']} is a special awards only.\n"); - /* Find their special award id */ - $q = mysql_query("SELECT award_awards.id,award_awards.name FROM - judges_specialaward_sel,award_awards - WHERE - award_awards.id=judges_specialaward_sel.award_awards_id - AND judges_specialaward_sel.users_id='{$j['id']}' - AND award_awards.year='{$config['FAIRYEAR']}'"); - echo mysql_error(); - if(mysql_num_rows($q) == 0) { - TRACE(" - NO special award selected! (removing special award only request)\n"); - $judges[$j['id']]['special_award_only'] = 'no'; - } else if(mysql_num_rows($q) > 1) { - TRACE(" - More than ONE special award selected (removing special award only request):\n"); - $judges[$j['id']]['special_award_only'] = 'no'; - } - } - - $judges[$j['id']]['special_awards'] = array(); - while($r = mysql_fetch_object($q)) { - TRACE(" {$r->name}\n"); - /* Add them to the SA judge list (modify the actual list, not - * $j, which is a copy */ - $judges[$j['id']]['special_awards'][] = $r->id; - } - - if($j['special_award_only'] == 'yes') { - /* Add to sa judge list, remove from the eligible judge list */ - $sa_judges[$j['id']] = $judges[$j['id']]; - unset($judges[$j['id']]); - } + /* If we get here, the judge is ok for div1 */ + $div1_judge_ids[] = $j['id']; } -TRACE("Loaded ".count($judges)." judges, and ".count($sa_judges)." special-award-only judges.\n"); -$jteam[0]['max_judges'] = count($judges); - +TRACE(count($div1_judge_ids)." judges available for round1 divisional\n"); function judges_to_teams_update($progress, $total) { @@ -488,48 +666,16 @@ function judges_to_teams_update($progress, $total) } set_status("Assigning Judges to Teams"); -$judge_ids = array_keys($judges); $e = $config['effort']; -$a = new annealer(count($jteam), 25, $e, 0.98, judges_cost_function, $judge_ids); +$a = new annealer(count($jteam), 25, $e, 0.98, judges_cost_function, $div1_judge_ids); $a->set_update_callback(judges_to_teams_update); $a->anneal(); -function pr_judge(&$jt, $jid) -{ - global $judges; - $j =& $judges[$jid]; - print(" - {$j['name']} (".join(' ', $j['languages']).')'); - print("("); - for($x=0; $xmax; - -TRACE("Max Judging Team Number is currently $max_jteam_num\n"); - for($x=1;$xbucket_cost[$x]} "); + print("Judging Team {$t['num']}: cost={$a->bucket_cost[$x]} "); $lang_array = $t['langs']; asort($lang_array); $langstr = implode(' ', $lang_array); @@ -541,47 +687,49 @@ for($x=1;$xbucket[$x]; for($y=0; $ybucket[0]; for($y=0; $ypick_move(); - - /* See if $b1,$i1 is movable */ - $id1 = $a->bucket[$b1][$i1]; - $j1 =& $sa_judges[$id1]; -// print("J1:"); -// print_r($j1); - if($j1['sa_only'] == 'yes') continue; - - if($i2 != -1) { - $id2 = $a->bucket[$b2][$i2]; - $j2 =& $sa_judges[$id2]; -// print("J2:"); -// print_r($j2); - if($j2['sa_only'] == 'yes') continue; - } - - return array($b1, $i1, $b2, $i2); - } -} - if($config['scheduler_enable_sa_scheduling'] == 'yes') { + TRACE("Finding judges for special award round(s)\n"); + foreach($round_special_awards as &$r) { + $r['available_judge_ids'] = array(); + } + + $total_judges = 0; + foreach($judges as &$j) { + foreach($round_special_awards as &$r) { + if(judge_available_for_round($j, $r) == true) { + $r['available_judge_ids'][] = $j['id']; + $total_judges++; /* It's ok to count the same judge twice */ + } + } + } + unset($j); + unset($r); + set_status("Creating Special Award Judging Teams (one team per award)"); + + /* Load special awards */ $q = "SELECT award_awards.name,award_awards.id FROM award_awards,award_types WHERE award_awards.year='{$config['FAIRYEAR']}' @@ -727,105 +918,214 @@ if($config['scheduler_enable_sa_scheduling'] == 'yes') { $sa_jteam[0]['max_judges'] = 0; $sa_jteam[0]['award_ids'] = array(); - /* Reload the jteam_id */ - $jteam_id = count($jteam); - $x=1; + $required_judges = 0; while($i = mysql_fetch_object($r)) { $projects = getProjectsNominatedForSpecialAward($i->id); - $max_jteam_num++; /* Pre-increment before using */ - + /* Construct an internal team for annealing, and create + * a DB team too */ $pids = array_keys($projects); - $sa_jteam[$x]['id'] = $jteam_id; + $sa_jteam[$x]['num'] = next_judges_teams_number(); + $sa_jteam[$x]['id'] = judge_team_create($sa_jteam[$x]['num'], $i->name); + /* Note, we use $x instead of the ID, because the DB id could be zero. */ $sa_jteam[$x]['projects'] = $pids; + $sa_jteam[$x]['round'] = NULL; $sa_jteam[$x]['sub'] = 0; $sa_jteam[$x]['langs'] = array(); $min = floor(count($pids) / $config['projects_per_special_award_judge']) + 1; $sa_jteam[$x]['min_judges'] = $min; $sa_jteam[$x]['max_judges'] = $min; $sa_jteam[$x]['award_ids'] = array($i->id); + $sa_jteam[$x]['name'] = $i->name; - $tn = "{$i->name}"; - /* Write this team to the DB */ - mysql_query("INSERT INTO judges_teams (num,name,autocreate_type_id,year) - VALUES ('$max_jteam_num','".mysql_escape_string($tn)."','1','{$config['FAIRYEAR']}')"); - $sa_jteam[$x]['id'] = mysql_insert_id(); - - /* Link the award to this team */ + $required_judges += $min; + + /* Link the award to this team */ mysql_query("INSERT INTO judges_teams_awards_link (award_awards_id,judges_teams_id,year) VALUES ('{$i->id}','{$sa_jteam[$x]['id']}','{$config['FAIRYEAR']}')"); - TRACE("Created Team: $tn {$sa_jteam[$x]['id']}\n"); - $jteam_id++; + TRACE("Created Team: {$i->name}, $min judges needed (db id:{$sa_jteam[$x]['id']}) \n"); $x++; } + TRACE("Total Judges: $total_judges, Required: $required_judges\n"); /* ====================================================================*/ - set_status("Assigning Judges to Special Award Teams\n"); + set_status("Assigning Special Award Teams to Special Award Round(s)\n"); + /* Compute how many judges each round needs based on the total number + * of needed judges, e.g. if SAround1 has 10 judges available and SAround2 + * has 20 judges available, and we total need 90 judges, then we + * want to assign jteams so that SAround1 has 30 slots, and SAround2 has + * 60 to balance the deficit */ + foreach($round_special_awards as &$r) { + $x = count($r['available_judge_ids']); + $target = ($x * $required_judges) / $total_judges; + $r['target_judges'] = $target; + TRACE("Round {$r['name']} should be assigned $target judge timeslots\n"); - $judge_ids = array_keys($sa_judges); - $e = $config['effort']; - $a = new annealer(count($sa_jteam), 25, $e, 0.98, judges_sa_cost_function, $judge_ids); - //$a->set_update_callback(judges_to_teams_update); - //$a->set_pick_move(judges_sa_pick_move); - $a->anneal(); + /* Setup for the next step, always add special award + * judge team 0 to ALL rounds */ + $r['jteam_ids'] = array(0); + $r['assigned_judges'] = 0; + } + unset($r); - $x=0; - unset($t); - unset($tid); - foreach($sa_jteam as $tid => $t) { - if($tid == 0) { + /* ====================================================================*/ + /* Scan the list of special awards, check each special award to see if + * it has special award only judges, we want those special awards pre-assigned + * to rounds where ALL SA-only judges are available, or, as best we can. */ + foreach($sa_jteam as $x=>&$jt) { + if($x == 0) continue; + + $sa_judges = array(); + foreach($round_special_awards as $i=>$r) { + $sa_round_count[$i] = 0; + } + + foreach($jt['award_ids'] as $aid) { + foreach($judges as $jid=>$j) { + if($j['special_award_only'] == 'no') continue; + if(in_array($aid, $j['special_awards'])) { + $sa_judges[] = $jid; + foreach($round_special_awards as $i=>$r) { +// TRACE("Checking {$j['name']} in round {$r['name']}\n"); + if(judge_available_for_round($j, $r)) { +// TRACE(" yes, round $i ++\n"); + $sa_round_count[$i]++; + + } + } + } + } + + } + + /* If there are no SA-only judges, skip the pre-assignment */ + if(count($sa_judges) == 0) continue; + + /* There are count($sa_judges), find the round + * with the highest count */ + $highest_count = 0; + $highest_offset = -1; + foreach($round_special_awards as $i=>$r) { + if($sa_round_count[$i] > $highest_count || $highest_offset == -1) { + $highest_count = $sa_round_count[$i]; + $highest_offset = $i; + } + } + /* Assign this jteam to that round */ + $round_special_awards[$highest_offset]['jteam_ids'][] = $x; + $round_special_awards[$highest_offset]['assigned_judges'] += $jt['min_judges']; + TRACE("Pre-assigning Team {$jt['name']} to Round {$round_special_awards[$highest_offset]['name']}\n"); + $jt['assigned'] = true; + } + unset($jt); + + /* Use a greedy algorithm to assign the remaining jteams. First sort + * the teams by the number of judges needed so those can be assigned + * first */ + function sa_cmp($a, $b) { + return $b['min_judges'] - $a['min_judges']; + } + uasort($sa_jteam, 'sa_cmp'); + + foreach($sa_jteam as $x=>$jt) { + if($x == 0) continue; + if($jt['assigned'] == true) continue; + + $highest = 0; + $highest_offset = -1; + /* Find the round with the highest missing judges, this works + * even if the $p computation is negative */ + foreach($round_special_awards as $o=>$r) { + $p = $r['target_judges'] - $r['assigned_judges']; +// TRACE(" Round {$r['name']} p=$p\n"); + if($highest_offset == -1 || $p > $highest) { + $highest = $p; + $highest_offset = $o; + } + } + /* Assign this jteam id to the special award round */ + $round_special_awards[$highest_offset]['jteam_ids'][] = $x; + $round_special_awards[$highest_offset]['assigned_judges'] += $jt['min_judges']; + } + + /* Now, anneal in each special award round */ + foreach($round_special_awards as $r) { + set_status("Assigning Judges in round {$r['name']}\n"); + + $current_jteam_ids = $r['jteam_ids']; + $judge_ids = $r['available_judge_ids']; + $e = $config['effort']; + $a = new annealer(count($r['jteam_ids']), 25, $e, 0.98, + judges_sa_cost_function, $judge_ids); + //$a->set_update_callback(judges_to_teams_update); + //$a->set_pick_move(judges_sa_pick_move); + $a->anneal(); + + $x=0; + + unset($t); + unset($tid); + foreach($r['jteam_ids'] as $tid) { + if($tid == 0) { + $x++; + continue; + } + + $t = &$sa_jteam[$tid]; + + print("Judging Team {$t['id']} \"{$t['name']}\": cost={$a->bucket_cost[$x]} #=({$t['min_judges']},{$t['max_judges']}) "); + + // print("langs=("); + /* $langstr=""; + for($y=0; $ybucket[$x]; + foreach($a->bucket[$x] as $jid) { + // pr_judge($t, $ids[$y]); + + $j = &$judges[$jid]; + print(" - {$j['name']}\n"); + + /* Link Judges to the judging team we just inserted */ + judge_team_add_judge($t['id'], $jid); + } $x++; - continue; } - - print("Judging Team {$t['id']}: cost={$a->bucket_cost[$x]} #=({$t['min_judges']},{$t['max_judges']}) "); - - // print("langs=("); - /* $langstr=""; - for($y=0; $ybucket[$x]; - for($y=0; $y$r->id, - "date"=>$r->date, - "starttime"=>substr($r->starttime,0,-3), - "endtime"=>substr($r->endtime,0,-3)); + "date"=>$r->date, + "starttime"=>substr($r->starttime,0,-3), + "endtime"=>substr($r->endtime,0,-3)); print(" ".$available_timeslots[$x]['starttime']." -> ". $available_timeslots[$x]['endtime']."\n"); $x++; @@ -968,13 +1268,19 @@ for($k=0; $k<$keys_count; $k++) { $a->set_pick_move(timeslot_pick_move); $a->anneal(); + printf(" "); + for($y=0;$y<$n_timeslots; $y++) { + printf("%4d ", $y+1); + } + printf("\n"); + for($x=0; $xbucket[$y][$x]; - TRACE(($y+1).":$jteam_id "); + printf("%4d ", $jteam[$jteam_id]['id']); if($jteam_id == 0) continue; @@ -992,7 +1298,7 @@ for($k=0; $k<$keys_count; $k++) { " '$pid', '{$config['FAIRYEAR']}')"); } - TRACE("\n"); + printf("\n"); } }