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"); } }