forked from science-ation/science-ation
1039 lines
31 KiB
PHP
1039 lines
31 KiB
PHP
<?
|
|
/*
|
|
This file is part of the 'Science Fair In A Box' project
|
|
SFIAB Website: http://www.sfiab.ca
|
|
|
|
Copyright (C) 2005 Sci-Tech Ontario Inc <info@scitechontario.org>
|
|
Copyright (C) 2005 James Grant <james@lightbox.org>
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public
|
|
License as published by the Free Software Foundation, version 2.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; see the file COPYING. If not, write to
|
|
the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
Boston, MA 02111-1307, USA.
|
|
*/
|
|
?>
|
|
<?
|
|
require("../common.inc.php");
|
|
require_once("../user.inc.php");
|
|
require("../questions.inc.php");
|
|
require("../projects.inc.php");
|
|
require("judges.inc.php");
|
|
require("anneal.inc.php");
|
|
|
|
if($_SERVER['SERVER_ADDR']) {
|
|
echo "This script must be run from the command line";
|
|
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 "<br />";
|
|
echo "<br />";
|
|
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 "<br />";
|
|
echo "<br />";
|
|
echo "<a href=\"judges_teams.php\">".i18n("Manage Judge Teams")."</a>";
|
|
echo "<br />";
|
|
echo "<a href=\"judges_teams_members.php\">".i18n("Manage Judge Members")."</a>";
|
|
echo "<br />";
|
|
echo "<br />";
|
|
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 "<br />";
|
|
echo "<br />";
|
|
|
|
*/
|
|
|
|
|
|
//function TRACE() { }
|
|
//function TRACE_R() { }
|
|
function TRACE($str) { print($str); }
|
|
function TRACE_R($array) { print_r($array); }
|
|
|
|
|
|
TRACE("<pre>");
|
|
|
|
|
|
function set_status($txt)
|
|
{
|
|
TRACE("Status: $txt\n");
|
|
mysql_query("UPDATE config SET val='$txt' WHERE
|
|
var='judge_scheduler_activity' AND year=0");
|
|
}
|
|
|
|
$set_percent_last_percent = -1;
|
|
function set_percent($n)
|
|
{
|
|
global $set_percent_last_percent;
|
|
$p = floor($n);
|
|
if($p == $set_percent_last_percent) return;
|
|
TRACE("Progress: $p\%\n");
|
|
mysql_query("UPDATE config SET val='$p' WHERE
|
|
var='judge_scheduler_percent' AND year=0");
|
|
$set_percent_last_percent = $p;
|
|
}
|
|
|
|
set_status("Initializing...");
|
|
set_percent(0);
|
|
|
|
/* The cost function is:
|
|
+ 50 * each judge below the min for each team
|
|
+ 10 * each judge above the max for each team
|
|
+ 2 * each level of preference away from the
|
|
max level for each judge
|
|
+ 40 if the team doesn't have a chair.
|
|
+ 25 for each memember on the team that can't speak the language
|
|
of the judging team
|
|
|
|
( ex: if a judge has selected LS->2, PS->0, CS->-1
|
|
then matching that judge with a:
|
|
LS = -4,
|
|
PS = 0,
|
|
CS = -2,
|
|
else = 0
|
|
)
|
|
*/
|
|
|
|
/* Compute the cost of adding a judge to a team */
|
|
|
|
function judges_cost_function($annealer, $bucket_id, $ids)
|
|
{
|
|
global $config;
|
|
global $jteam;
|
|
global $judges;
|
|
/* Bucket ID is the team number */
|
|
/* ids are the judge ids currently in the bucket */
|
|
|
|
// TRACE("Bucket id=$bucket_id, ids=");
|
|
// TRACE_R($ids);
|
|
|
|
$cost = 0;
|
|
$have_chair = 0;
|
|
|
|
if($bucket_id == 0) {
|
|
/* This is the placeholder */
|
|
$cost = count($ids) * 5;
|
|
// TRACE("Extra judge team cost=$cost\n");
|
|
return $cost;
|
|
}
|
|
|
|
|
|
$t =& $jteam[$bucket_id];
|
|
|
|
/* Compute the over max / under min costs */
|
|
$c = count($ids);
|
|
$min = ($c < $t['min_judges']) ? $t['min_judges'] - $c : 0;
|
|
$max = ($c > $t['max_judges']) ? $c - $t['max_judges'] : 0;
|
|
$cost += $min * 50;
|
|
$cost += $max * 10;
|
|
|
|
// TRACE("Under min=$min, over max=$max\n");
|
|
|
|
/* For each judge on the team, score their preferences */
|
|
for($x=0; $x<count($ids); $x++) {
|
|
$j =& $judges[$ids[$x]];
|
|
/* Get the division, and see where it fits with this
|
|
* judges preferences */
|
|
$cpref = 0;
|
|
for($y=0; $y < count($t['cats']); $y++) {
|
|
$l = $t['cats'][$y];
|
|
/* Lookup the judge cat pref for this category */
|
|
$pref = -$j['catprefs'][$l] + 2;
|
|
/* $pref = 0 (best match) --- 4 (worst match) */
|
|
$cpref += $pref;
|
|
}
|
|
$dpref = 0;
|
|
for($y=0; $y < count($t['divs']); $y++) {
|
|
$l = $t['divs'][$y];
|
|
/* Lookup the judge cat pref for this category */
|
|
$pref = -$j['divprefs'][$l] + 2;
|
|
/* $pref = 0 (best match) --- 4 (worst match) */
|
|
$dpref += $pref;
|
|
}
|
|
|
|
// TRACE("Judge {$ids[$x]}({$j['name']}) cp=$cpref, dp=$dpref\n");
|
|
|
|
$cost += 2 * $cpref;
|
|
$cost += 2 * $dpref;
|
|
|
|
/* See if the judge is willing to chair a team */
|
|
if($j['willing_chair'] == 'yes') $have_chair = 1;
|
|
|
|
/* For each lang the team needs that the judge doesn't have,
|
|
* increase the cost */
|
|
for($y=0; $y < count($t['langs']); $y++) {
|
|
$l = $t['langs'][$y];
|
|
if(!in_array($l, $j['languages'])) $cost += 25;
|
|
}
|
|
}
|
|
/* 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;
|
|
|
|
// TRACE("Team $bucket_id, cost is $cost\n");
|
|
|
|
return $cost;
|
|
}
|
|
|
|
|
|
$current_jdiv = array();
|
|
|
|
|
|
function jdiv_compute_cost($annealer, $bucket_id, $ids)
|
|
{
|
|
/* IDS is a list of project ids for a judging team */
|
|
global $current_jdiv;
|
|
|
|
$cost = 0;
|
|
$t_div = array();
|
|
$t_cat = array();
|
|
$t_lang = array();
|
|
|
|
/* Foreach project this jteam is judging, record the
|
|
* div/cat/lang */
|
|
for($x=0; $x<count($ids); $x++) {
|
|
$proj =& $current_jdiv['projects'][$ids[$x]];
|
|
if(!in_array($proj['div'],$t_div)) $t_div[] = $proj['div'];
|
|
if(!in_array($proj['cat'],$t_cat)) $t_cat[] = $proj['cat'];
|
|
if(!in_array($proj['lang'],$t_lang)) $t_lang[] = $proj['lang'];
|
|
}
|
|
/* Square the project count for highter penalties for more projects */
|
|
$cost += floor (abs(count($ids) - $annealer->items_per_bucket)) * 100;
|
|
/* Score 100 pts for multiple languages */
|
|
$cost += (count($t_lang) - 1) * 75;
|
|
/* Score 25pts for multiple divs/cats */
|
|
$cost += (count($t_div) - 1) * 25;
|
|
$cost += (count($t_cat) - 1) * 25;
|
|
|
|
/* Score +200 pts for each duplicate project this team is judging, we
|
|
* really don't want a jteam judging the same project twice */
|
|
for($x=0; $x<count($ids) - 1; $x++) {
|
|
for($y=$x+1; $y<count($ids); $y++) {
|
|
if($ids[$x] == $ids[$y]) $cost += 200;
|
|
}
|
|
}
|
|
return $cost;
|
|
}
|
|
|
|
|
|
set_status("Loading Data From Database...");
|
|
TRACE("\n\n");
|
|
$div = array();
|
|
TRACE("Loading Project Divisions...\n");
|
|
$q=mysql_query("SELECT * FROM projectdivisions WHERE year='".$config['FAIRYEAR']."' ORDER BY id");
|
|
while($r=mysql_fetch_object($q))
|
|
{
|
|
$divshort[$r->id]=$r->division_shortform;
|
|
$div[$r->id]=$r->division;
|
|
TRACE(" {$r->id} - {$div[$r->id]}\n");
|
|
}
|
|
|
|
TRACE("Loading Project Age Categories...\n");
|
|
$cat = array();
|
|
$q=mysql_query("SELECT * FROM projectcategories WHERE year='".$config['FAIRYEAR']."' ORDER BY id");
|
|
while($r=mysql_fetch_object($q)) {
|
|
$cat[$r->id]=$r->category;
|
|
TRACE(" {$r->id} - {$r->category}\n");
|
|
}
|
|
|
|
TRACE("Loading Languages...\n");
|
|
$langr = array();
|
|
$q=mysql_query("SELECT * FROM languages WHERE active='Y'");
|
|
while($r=mysql_fetch_object($q)) {
|
|
$langr[$r->lang] = $r->langname;
|
|
TRACE(" {$r->lang} - {$r->langname}\n");
|
|
}
|
|
|
|
$jdiv = array();
|
|
TRACE("Loading Judging Division Configuration and Projects...\n");
|
|
$q=mysql_query("SELECT * FROM judges_jdiv");
|
|
while($r=mysql_fetch_object($q)) {
|
|
/* Ignore jdiv 0 (all unassigned div/cats) */
|
|
if($r->jdiv_id == 0) continue;
|
|
|
|
$jdiv[$r->jdiv_id]['config'][] = array('div' => $r->projectdivisions_id,
|
|
'cat' => $r->projectcategories_id,
|
|
'lang' => $r->lang);
|
|
}
|
|
|
|
$keys = array_keys($jdiv);
|
|
for($k=0; $k<count($keys); $k++) {
|
|
$jdiv_id = $keys[$k];
|
|
TRACE(" $jdiv_id\t- ");
|
|
$jdiv[$jdiv_id]['projects'] = array();
|
|
for($x=0;$x<count($jdiv[$jdiv_id]['config']); $x++) {
|
|
$d = $jdiv[$jdiv_id]['config'][$x];
|
|
if($x > 0) TRACE("\t- ");
|
|
TRACE($cat[$d['cat']]." ".$div[$d['div']]." - ".$langr[$d['lang']]);
|
|
$qp = mysql_query("SELECT projects.* FROM projects, registrations WHERE ".
|
|
" projects.year='".$config['FAIRYEAR']."' AND ".
|
|
" projectdivisions_id='{$d['div']}' AND ".
|
|
" projectcategories_id='{$d['cat']}' AND ".
|
|
" language='{$d['lang']}' AND " .
|
|
" registrations.id = projects.registrations_id " .
|
|
getJudgingEligibilityCode()
|
|
);
|
|
$count = 0;
|
|
while($rp = mysql_fetch_object($qp)) {
|
|
$jdiv[$jdiv_id]['projects'][$rp->id] = array(
|
|
'div' => $d['div'],
|
|
'cat' => $d['cat'],
|
|
'lang' => $d['lang']);
|
|
$count++;
|
|
}
|
|
TRACE(" ($count projects)\n");
|
|
}
|
|
if(count($jdiv[$jdiv_id]['projects']) == 0) {
|
|
TRACE("\t- This div has no projects, removing.\n");
|
|
unset($jdiv[$jdiv_id]);
|
|
}
|
|
}
|
|
|
|
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; $k<count($keys); $k++) {
|
|
$jdiv_id = $keys[$k];
|
|
$c = count($jdiv[$jdiv_id]['projects']);
|
|
$t=ceil($c/$config['max_projects_per_team']*$config['times_judged']);
|
|
if($t < $config['times_judged']) $t = $config['times_judged'];
|
|
TRACE(" $jdiv_id has $c projects, requires $t judging teams\n");
|
|
$jdiv[$jdiv_id]['num_jteams'] = $t;
|
|
}
|
|
|
|
$jteam = array();
|
|
$jteam_id = 0;
|
|
/* Create one more jteam, for anyone the annealer doesn't want to place */
|
|
$jteam[$jteam_id]['id'] = $jteam_id;
|
|
$jteam[$jteam_id]['projects'] = array();
|
|
$jteam[$jteam_id]['divs'] = array();
|
|
$jteam[$jteam_id]['cats'] = array();
|
|
$jteam[$jteam_id]['langs'] = array();
|
|
$jteam[$jteam_id]['min_judges'] = 0;
|
|
$jteam[$jteam_id]['max_judges'] = 0;
|
|
$jteam_id++;
|
|
|
|
set_status("Assigning projects to judging teams");
|
|
$keys = array_keys($jdiv);
|
|
for($k=0; $k<count($keys); $k++) {
|
|
$jdiv_id = $keys[$k];
|
|
TRACE("Judging Division $jdiv_id ({$jdiv[$jdiv_id]['num_jteams']} teams): \n");
|
|
$project_ids = array();
|
|
for($x=0; $x<$config['times_judged']; $x++) {
|
|
$project_ids = array_merge($project_ids, array_keys($jdiv[$jdiv_id]['projects']) );
|
|
}
|
|
$current_jdiv = $jdiv[$jdiv_id];
|
|
|
|
$e = 100 + 10 * ($config['effort'] / 1000);
|
|
$a = new annealer($jdiv[$jdiv_id]['num_jteams'], 125, $e, 0.9,
|
|
jdiv_compute_cost, $project_ids);
|
|
$a->anneal();
|
|
|
|
$jdiv[$jdiv_id]['jteams'] = array();
|
|
for($x=0;$x<$a->num_buckets; $x++) {
|
|
$bkt = $a->bucket[$x];
|
|
TRACE(" SubTeam $x:\n");
|
|
$jdiv[$jdiv_id]['jteams'][] = $jteam_id;
|
|
|
|
$jteam[$jteam_id]['id'] = $jteam_id;
|
|
$jteam[$jteam_id]['projects'] = $a->bucket[$x];
|
|
$jteam[$jteam_id]['sub'] = $x;
|
|
$jteam[$jteam_id]['jdiv_id'] = $jdiv_id;
|
|
$jteam[$jteam_id]['divs'] = array();
|
|
$jteam[$jteam_id]['cats'] = array();
|
|
$jteam[$jteam_id]['langs'] = array();
|
|
$jteam[$jteam_id]['min_judges'] = $config['min_judges_per_team'];
|
|
$jteam[$jteam_id]['max_judges'] = $config['max_judges_per_team'];
|
|
|
|
for($y=0;$y<count($bkt); $y++) {
|
|
$projid = $bkt[$y];
|
|
$p = $jdiv[$jdiv_id]['projects'][$projid];
|
|
TRACE(" $projid - ".$cat[$p['cat']]." ".$div[$p['div']]." - ".$langr[$p['lang']]."\n");
|
|
if(!in_array($p['cat'], $jteam[$jteam_id]['cats'])) {
|
|
$jteam[$jteam_id]['cats'][] = $p['cat'];
|
|
}
|
|
if(!in_array($p['div'], $jteam[$jteam_id]['divs'])) {
|
|
$jteam[$jteam_id]['divs'][] = $p['div'];
|
|
}
|
|
if(!in_array($p['lang'], $jteam[$jteam_id]['langs'])) {
|
|
$jteam[$jteam_id]['langs'][] = $p['lang'];
|
|
}
|
|
}
|
|
$jteam_id++;
|
|
}
|
|
}
|
|
|
|
TRACE("There are ".(count($jteam) - 1)." judging teams\n");
|
|
|
|
|
|
$willing_chair_question_id = questions_find_question_id("judgereg", $config['FAIRYEAR'], "Willing Chair");
|
|
printf("Judge Willing Chair = Question ID $willing_chair_question_id\n");
|
|
|
|
/* Clean out the judging teams that were autocreated */
|
|
TRACE("Deleting autocreated divisional and special award judging teams:");
|
|
$q = mysql_query("SELECT * FROM judges_teams WHERE autocreate_type_id=1 AND year={$config['FAIRYEAR']}");
|
|
while($r = mysql_fetch_object($q)) {
|
|
$jteam_id = $r->id;
|
|
print(" $jteam_id");
|
|
/* Clean out the judges_teams_link */
|
|
mysql_query("DELETE FROM judges_teams_link WHERE judges_teams_id='$jteam_id' AND year={$config['FAIRYEAR']}");
|
|
print mysql_error();
|
|
/* Awards */
|
|
mysql_query("DELETE FROM judges_teams_awards_link WHERE judges_teams_id='$jteam_id' AND year={$config['FAIRYEAR']}");
|
|
print mysql_error();
|
|
/* Timeslots */
|
|
mysql_query("DELETE FROM judges_teams_timeslots_link WHERE judges_teams_id='$jteam_id' AND year={$config['FAIRYEAR']}");
|
|
print mysql_error();
|
|
/* Timeslots projects */
|
|
mysql_query("DELETE FROM judges_teams_timeslots_projects_link WHERE judges_teams_id='$jteam_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");
|
|
|
|
$q=mysql_query("SELECT judges.* FROM judges,judges_years WHERE ".
|
|
"complete='yes' AND deleted='no' ".
|
|
" AND judges_years.year='{$config['FAIRYEAR']}' ".
|
|
" AND judges_years.judges_id=judges.id"
|
|
);
|
|
|
|
$judges=array();
|
|
$sa_judges = array();
|
|
|
|
while($r=mysql_fetch_object($q))
|
|
{
|
|
unset($divprefs);
|
|
unset($catprefs);
|
|
unset($langprefs);
|
|
|
|
/* Try to fetch a team link ID for this judge, if we can, we don't want to use this judge
|
|
* in the divisional awards, they are already assigned to soemthing */
|
|
$q2 = mysql_query("SELECT judges_id FROM judges_teams_link WHERE ".
|
|
"judges_id='$r->id' ".
|
|
" AND year='".$config['FAIRYEAR']."'");
|
|
if(mysql_num_rows($q2) != 0) {
|
|
TRACE(" {$r->firstname} {$r->lastname} is already on a judging team, skipping.\n");
|
|
continue;
|
|
}
|
|
|
|
//get category preferences
|
|
$q2=mysql_query("SELECT * FROM judges_catpref WHERE judges_id='$r->id' AND year='".$config['FAIRYEAR']."' ORDER BY projectcategories_id");
|
|
$catprefs=array();
|
|
while($r2=mysql_fetch_object($q2))
|
|
$catprefs[$r2->projectcategories_id]=$r2->rank;
|
|
|
|
//get division preferences
|
|
$q2=mysql_query("SELECT * FROM judges_expertise WHERE judges_id='$r->id' AND year='".$config['FAIRYEAR']."' AND projectsubdivisions_id IS NULL ORDER BY projectdivisions_id");
|
|
//the areas of expertise are ranked from 1 to 5, and we need them as -2,-1,0,1,2 so we simply subtract 3
|
|
$divprefs=array();
|
|
while($r2=mysql_fetch_object($q2))
|
|
$divprefs[$r2->projectdivisions_id]=$r2->val-3;
|
|
|
|
$langprefs = array();
|
|
$q3=mysql_query("SELECT * from judges_languages WHERE judges_id='$r->id'");
|
|
while($r3=mysql_fetch_object($q3))
|
|
$langprefs[]=$r3->languages_lang;
|
|
|
|
$q2 = mysql_query("SELECT answer FROM question_answers WHERE ".
|
|
" registrations_id='{$r->id}' AND ".
|
|
" questions_id='$willing_chair_question_id' AND ".
|
|
" year='{$config['FAIRYEAR']}' ");
|
|
mysql_error();
|
|
$willing_chair = 'no';
|
|
if(mysql_num_rows($q2) == 1) {
|
|
$r2 = mysql_fetch_object($q2);
|
|
if($r2->answer == 'yes') $willing_chair = 'yes';
|
|
}
|
|
|
|
$sa_only = 'no';
|
|
if($r->typepref == 'speconly') $sa_only = 'yes';
|
|
$sa_sel = array();
|
|
|
|
|
|
if($sa_only == 'yes') {
|
|
TRACE("Judge [{$r->firstname} {$r->lastname}] is a special awards only.\n");
|
|
/* Find their special award id */
|
|
$qq = 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.judges_id='{$r->id}'
|
|
AND judges_specialaward_sel.year='{$config['FAIRYEAR']}'
|
|
AND award_awards.year='{$config['FAIRYEAR']}'");
|
|
echo mysql_error();
|
|
if(mysql_num_rows($qq) == 0) {
|
|
TRACE(" - NO special award selected! (removing special award only request)\n");
|
|
$sa_only = 'no';
|
|
} else if(mysql_num_rows($qq) > 1) {
|
|
TRACE(" - More than ONE special award selected (removing special award only request):\n");
|
|
$sa_only = 'no';
|
|
}
|
|
while($rr = mysql_fetch_object($qq)) {
|
|
TRACE(" ".$rr->name."\n");
|
|
$sa_sel[] = $rr->id;
|
|
}
|
|
}
|
|
|
|
$j=array(
|
|
"judges_id"=>"$r->id",
|
|
"name"=>"$r->firstname $r->lastname",
|
|
"years_school"=>$r->years_school,
|
|
"years_regional"=>$r->years_regional,
|
|
"years_national"=>$r->years_national,
|
|
"willing_chair"=>$willing_chair,
|
|
"divprefs"=>$divprefs,
|
|
"catprefs"=>$catprefs,
|
|
"languages"=>$langprefs,
|
|
"sa_only"=>$sa_only,
|
|
"sa_sel"=>$sa_sel,
|
|
);
|
|
|
|
/* If it's a special award only judge, keep them
|
|
* out of the judges list for the divisional annealer */
|
|
if($sa_only == 'yes') {
|
|
$sa_judges[$r->id] = $j;
|
|
} else {
|
|
$judges[$r->id] = $j;
|
|
}
|
|
|
|
|
|
}
|
|
TRACE("Loaded ".count($judges)." judges.\n");
|
|
$jteam[0]['max_judges'] = count($judges);
|
|
|
|
|
|
function judges_to_teams_update($progress, $total)
|
|
{
|
|
set_percent(($progress * 50) / $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->set_update_callback(judges_to_teams_update);
|
|
$a->anneal();
|
|
|
|
|
|
|
|
function pr_judge(&$jt, $jid)
|
|
{
|
|
global $judges;
|
|
$j =& $judges[$jid];
|
|
print(" - {$j['name']} (");
|
|
for($x=0; $x<count($j['languages']); $x++) {
|
|
print($j['languages'][$x]. " ");
|
|
}
|
|
print(")");
|
|
print("(");
|
|
for($x=0; $x<count($jt['cats']); $x++) {
|
|
$pref=$j['catprefs'][$jt['cats'][$x]];
|
|
print("c{$jt['cats'][$x]}=$pref ");
|
|
}
|
|
for($x=0; $x<count($jt['divs']); $x++) {
|
|
$pref=$j['divprefs'][$jt['divs'][$x]];
|
|
print("d{$jt['divs'][$x]}=$pref ");
|
|
}
|
|
print(")");
|
|
if($j['willing_chair'] == 'yes') {
|
|
print(" (chair) ");
|
|
}
|
|
|
|
print("\n");
|
|
}
|
|
|
|
|
|
/* Find the maximum judging team number */
|
|
$q = mysql_query("SELECT MAX(num) as max FROM judges_teams WHERE year='{$config['FAIRYEAR']}'");
|
|
$r = mysql_fetch_object($q);
|
|
$max_jteam_num = $r->max;
|
|
|
|
TRACE("Max Judging Team Number is currently $max_jteam_num\n");
|
|
|
|
for($x=1;$x<count($jteam); $x++) {
|
|
$t =& $jteam[$x];
|
|
$max_jteam_num++;
|
|
print("Judging Team $max_jteam_num: cost={$a->bucket_cost[$x]} ");
|
|
$lang_array = $t['langs'];
|
|
asort($lang_array);
|
|
$langstr = implode(' ', $lang_array);
|
|
|
|
print("langs=($langstr)");
|
|
print("cats=(");
|
|
$catstr="";
|
|
for($y=0; $y<count($t['cats']); $y++) {
|
|
print("c".$t['cats'][$y]. " ");
|
|
if($y!=0) $catstr .= "+";
|
|
$catstr .= $cat[$t['cats'][$y]];
|
|
}
|
|
print(")");
|
|
print("divs=(");
|
|
$divstr="";
|
|
for($y=0; $y<count($t['divs']); $y++) {
|
|
print("d".$t['divs'][$y]. " ");
|
|
if($y!=0) $divstr.="/";
|
|
$divstr .= $div[$t['divs'][$y]];
|
|
}
|
|
print(")\n");
|
|
|
|
/* Add this judging team to the database */
|
|
$tn = $catstr." ".$divstr." (".$langstr.") ".($t['sub']+1);
|
|
mysql_query("INSERT INTO judges_teams (num,name,autocreate_type_id,year) ".
|
|
" VALUES ('$max_jteam_num','$tn','1','{$config['FAIRYEAR']}')");
|
|
|
|
$team_id=mysql_insert_id();
|
|
$t['team_id'] = $team_id;
|
|
|
|
$ids = $a->bucket[$x];
|
|
for($y=0; $y<count($ids); $y++) {
|
|
pr_judge($t, $ids[$y]);
|
|
$j =& $judges[$ids[$y]];
|
|
|
|
/* Link Judges to the judging team we just inserted */
|
|
mysql_query("INSERT INTO judges_teams_link ".
|
|
" (judges_id,judges_teams_id,captain,year) ".
|
|
" VALUES ('{$ids[$y]}','$team_id','{$j['willing_chair']}',".
|
|
"'{$config['FAIRYEAR']}')");
|
|
}
|
|
|
|
/* Get the original jdiv that this team was created from. The exact
|
|
* breakdown of each and every div/cat/lang that this team is judging
|
|
* is in the jdiv['config'] array */
|
|
$jd = $jdiv[$t['jdiv_id']];
|
|
for($y=0; $y<count($jd['config']); $y++) {
|
|
$cfg = $jd['config'][$y];
|
|
$q=mysql_query("SELECT award_awards.id FROM
|
|
award_awards,
|
|
award_awards_projectcategories,
|
|
award_awards_projectdivisions
|
|
WHERE
|
|
award_awards.year='{$config['FAIRYEAR']}'
|
|
AND award_awards.id=award_awards_projectcategories.award_awards_id
|
|
AND award_awards.id=award_awards_projectdivisions.award_awards_id
|
|
AND award_awards_projectcategories.projectcategories_id='{$cfg['cat']}'
|
|
AND award_awards_projectdivisions.projectdivisions_id='{$cfg['div']}'
|
|
AND award_awards.award_types_id='1'
|
|
");
|
|
if(mysql_num_rows($q)!=1) {
|
|
echo error(i18n("Cannot find award for %1 - %2",array($cat[$cfg['cat']],$div[$cfg['div']])));
|
|
} else {
|
|
$r=mysql_fetch_object($q);
|
|
mysql_query("INSERT INTO judges_teams_awards_link (award_awards_id,judges_teams_id,year) VALUES ('$r->id','$team_id','{$config['FAIRYEAR']}')");
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
print("Unused Judges:\n");
|
|
$ids = $a->bucket[0];
|
|
for($y=0; $y<count($ids); $y++) {
|
|
pr_judge($jteam[0], $ids[$y]);
|
|
$sa_judges[$ids[$y]] = $judges[$ids[$y]];
|
|
}
|
|
|
|
/* ====================================================================*/
|
|
/* Two functions for the Special Award Annealer, if special award
|
|
* scheduling is disabled, these will never get called */
|
|
function judges_sa_cost_function($annealer, $bucket_id, $ids)
|
|
{
|
|
global $sa_jteam;
|
|
global $sa_judges;
|
|
|
|
/* Bucket ID is the team number */
|
|
/* ids are the judge ids currently in the bucket */
|
|
|
|
$cost = 0;
|
|
if($bucket_id == 0) {
|
|
/* This is the placeholder */
|
|
$cost = count($ids) * 50;
|
|
return $cost;
|
|
}
|
|
|
|
$t =& $sa_jteam[$bucket_id];
|
|
|
|
/* Compute the over max / under min costs */
|
|
$c = count($ids);
|
|
$min = ($c < $t['min_judges']) ? $t['min_judges'] - $c : 0;
|
|
$max = ($c > $t['max_judges']) ? $c - $t['max_judges'] : 0;
|
|
$cost += $min * 50;
|
|
$cost += $max * 10;
|
|
|
|
/* For each judge on the team, score their preferences */
|
|
for($x=0; $x<count($ids); $x++) {
|
|
$j =& $sa_judges[$ids[$x]];
|
|
$apref = 0;
|
|
|
|
/* See if the sa_jteam award id (what the team is judging)
|
|
* is in the judges selection list */
|
|
/* Run through all awards this team is judging */
|
|
foreach($t['award_ids'] as $aid) {
|
|
if(in_array($aid, $j['sa_sel'])) {
|
|
/* This judge wants to judge this award */
|
|
/* No cost */
|
|
} else {
|
|
if($j['sa_only'] == 'yes') {
|
|
/* This judge is for an award, but
|
|
* NOT assigned to the proper one,
|
|
* HUGE cost */
|
|
$cost += 500;
|
|
}
|
|
$apref++;
|
|
}
|
|
}
|
|
$cost += 5 * $apref;
|
|
}
|
|
// TRACE("Team $bucket_id, cost is $cost\n");
|
|
|
|
return $cost;
|
|
}
|
|
function judges_sa_pick_move($a)
|
|
{
|
|
global $sa_judges;
|
|
/* Use the existing pick move, but we never want to
|
|
* select a judge that has sa_only=='yes', they are
|
|
* already in the correct place and are unmovable */
|
|
/* FIXME: this will spin forever if there aren't at least
|
|
* 2 judges to swap */
|
|
while(1) {
|
|
list($b1, $i1, $b2, $i2) = $a->pick_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') {
|
|
set_status("Creating Special Award Judging Teams (one team per award)");
|
|
$q = "SELECT award_awards.name,award_awards.id FROM award_awards,award_types
|
|
WHERE
|
|
award_awards.year='{$config['FAIRYEAR']}'
|
|
AND award_types.id=award_awards.award_types_id
|
|
AND award_awards.schedule_judges='yes'
|
|
AND award_types.year='{$config['FAIRYEAR']}'
|
|
AND award_types.type='Special'
|
|
";
|
|
$r = mysql_query($q);
|
|
print(mysql_error());
|
|
/* sa_jteam for leftover judges, if any */
|
|
$sa_jteam = array();
|
|
$sa_jteam[0]['id'] = 0;
|
|
$sa_jteam[0]['projects'] = array();
|
|
$sa_jteam[0]['langs'] = array();
|
|
$sa_jteam[0]['min_judges'] = 0;
|
|
$sa_jteam[0]['max_judges'] = 0;
|
|
$sa_jteam[0]['award_ids'] = array();
|
|
|
|
$x=1;
|
|
while($i = mysql_fetch_object($r)) {
|
|
$projects = getProjectsNominatedForSpecialAward($i->id);
|
|
|
|
$max_jteam_num++; /* Pre-increment before using */
|
|
|
|
$pids = array_keys($projects);
|
|
$sa_jteam[$x]['id'] = $jteam_id;
|
|
$sa_jteam[$x]['projects'] = $pids;
|
|
$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);
|
|
|
|
$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 */
|
|
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++;
|
|
$x++;
|
|
}
|
|
|
|
|
|
|
|
/* ====================================================================*/
|
|
set_status("Assigning Judges to Special Award Teams\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();
|
|
|
|
|
|
$x=0;
|
|
foreach($sa_jteam as $tid => $t) {
|
|
if($tid == 0) {
|
|
$x++;
|
|
continue;
|
|
}
|
|
print("Judging Team {$t['id']}: cost={$a->bucket_cost[$x]} #=({$t['min_judges']},{$t['max_judges']}) ");
|
|
// print("langs=(");
|
|
/* $langstr="";
|
|
for($y=0; $y<count($t['langs']); $y++) {
|
|
if($y != 0) $langstr .= " ";
|
|
$langstr .= $t['langs'][$y];
|
|
}
|
|
print("$langstr)");*/
|
|
print("\n");
|
|
|
|
$ids = $a->bucket[$x];
|
|
for($y=0; $y<count($ids); $y++) {
|
|
// pr_judge($t, $ids[$y]);
|
|
|
|
$j =& $sa_judges[$ids[$y]];
|
|
print(" - {$j['name']}\n");
|
|
|
|
/* Link Judges to the judging team we just inserted */
|
|
mysql_query("INSERT INTO judges_teams_link
|
|
(judges_id,judges_teams_id,captain,year)
|
|
VALUES ('{$ids[$y]}','{$t['id']}',
|
|
'{$j['willing_chair']}',
|
|
'{$config['FAIRYEAR']}')");
|
|
}
|
|
$x++;
|
|
}
|
|
}
|
|
|
|
/* Resume normal flow now */
|
|
|
|
/* ====================================================================*/
|
|
set_status("Assigning Judging Teams and Projects to Timeslots");
|
|
|
|
TRACE("Loading Timeslot Data\n");
|
|
$available_timeslots=array();
|
|
$q=mysql_query("SELECT * FROM judges_timeslots WHERE ".
|
|
" year='{$config['FAIRYEAR']}' AND ".
|
|
" allowdivisional='yes' ORDER BY date,starttime");
|
|
$x=0;
|
|
while($r=mysql_fetch_object($q)) {
|
|
$available_timeslots[]=array("id"=>$r->id,
|
|
"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++;
|
|
}
|
|
|
|
$n_timeslots = count($available_timeslots);
|
|
|
|
|
|
/* First, check to see if the project is being judged 3 or
|
|
* more times in a row, OR, if it has large gaps that aren't
|
|
at the end of the judging */
|
|
/* I'm going to leave this here, for now, we shoudl do something like
|
|
* this at some point in evaluating projects, but right now
|
|
* the randomness is pretty good. */
|
|
/* for($x=0; $x<count($project_index); $x++) {
|
|
$i_start = $x * $n_timeslots;
|
|
$i_end = ($x+1) * $n_timeslots;
|
|
|
|
$z_count = 0;
|
|
$r_count = 0;
|
|
for($y=$i_start; $y<$i_end; $y++) {
|
|
$jteam_id = $ids[$y];
|
|
|
|
if($jteam_id == 0) {
|
|
$z_count++;
|
|
$r_count=0;
|
|
} else {*/
|
|
/* Do the z_count cost here so we don;t
|
|
* count any zcount cost for the end
|
|
* of the timetable */
|
|
/* if($z_count > 2) $cost += $z_count;
|
|
$r_count++;
|
|
$z_count=0;
|
|
if($r_count > 2) $cost += $r_count;
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
|
|
function timeslot_pick_move($a)
|
|
{
|
|
/* Use the existing pick move, but we want the item numbers
|
|
* in each bucket to always be the same */
|
|
list($b1, $i1, $b2, $i2) = $a->pick_move();
|
|
$i2 = $i1;
|
|
return array($b1, $i1, $b2, $i2);
|
|
}
|
|
|
|
|
|
function timeslot_cost_function($annealer, $bucket_id, $ids)
|
|
{
|
|
$cost = 0;
|
|
|
|
/* Check to make sure a judge isn't judging two projects
|
|
* at the same time */
|
|
$n_pids = count($ids);
|
|
for($x=0; $x<$n_pids-1; $x++) {
|
|
$jteam_id1 = $ids[$x];
|
|
if($jteam_id1 == 0) continue;
|
|
for($y=$x+1; $y<$n_pids; $y++) {
|
|
$jteam_id2 = $ids[$y];
|
|
if($jteam_id1 == $jteam_id2)
|
|
$cost += 50;
|
|
}
|
|
}
|
|
return $cost;
|
|
}
|
|
|
|
|
|
|
|
|
|
$keys = array_keys($jdiv);
|
|
$keys_count = count($keys);
|
|
for($k=0; $k<$keys_count; $k++) {
|
|
$jdiv_id = $keys[$k];
|
|
|
|
$pids = array_keys($jdiv[$jdiv_id]['projects']);
|
|
$n_projects = count($pids);
|
|
|
|
if($n_projects == 0) continue;
|
|
|
|
unset($project_rlookup);
|
|
$project_rlookup = array();
|
|
|
|
for($x=0; $x<count($pids); $x++) {
|
|
$project_rlookup[$pids[$x]] = $x;
|
|
}
|
|
|
|
$current_jdiv = $jdiv_id;
|
|
|
|
printf($n_projects. " projects in this jdiv\n");
|
|
unset($jteams_ids);
|
|
$jteams_ids = array();
|
|
/* Pad to the correct length */
|
|
for($x=0; $x<($n_timeslots * count($pids)); $x++)
|
|
$jteams_ids[] = 0;
|
|
|
|
/* Fill out the jteam array with a jteam_id for each time the
|
|
* jteam_id is supposed to judge a project */
|
|
$jteams = $jdiv[$jdiv_id]['jteams'];
|
|
foreach($jteams as $jteam_id) {
|
|
for($y=0;$y<count($jteam[$jteam_id]['projects']); $y++) {
|
|
$pid = $jteam[$jteam_id]['projects'][$y];
|
|
$idx = $project_rlookup[$pid];
|
|
|
|
for($o = $idx; ; $o+= $n_projects) {
|
|
if($jteams_ids[$o] != 0) continue;
|
|
|
|
$jteams_ids[$o] = $jteam_id;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
print("Jteams ids len=".count($jteams_ids));
|
|
print("\n");
|
|
|
|
set_percent(50 + ($k / $keys_count) * 50);
|
|
|
|
$e = 500 + 50 * ($config['effort'] / 1000);
|
|
$a = new annealer($n_timeslots, 100, $e, 0.98, timeslot_cost_function, $jteams_ids);
|
|
$a->set_pick_move(timeslot_pick_move);
|
|
$a->anneal();
|
|
|
|
for($x=0; $x<count($pids); $x++) {
|
|
$pid = $pids[$x];
|
|
TRACE("Project $pid: ");
|
|
|
|
for($y=0;$y<$n_timeslots; $y++) {
|
|
$jteam_id = $a->bucket[$y][$x];
|
|
TRACE(($y+1).":$jteam_id ");
|
|
|
|
if($jteam_id == 0) continue;
|
|
|
|
/* if jteam_id isn't 0, instert it into the db */
|
|
mysql_query("INSERT INTO judges_teams_timeslots_link ".
|
|
" (judges_teams_id,judges_timeslots_id,year)".
|
|
" VALUES ('{$jteam[$jteam_id]['team_id']}', ".
|
|
" '{$available_timeslots[$y]['id']}', ".
|
|
" '{$config['FAIRYEAR']}')");
|
|
|
|
mysql_query("INSERT INTO judges_teams_timeslots_projects_link ".
|
|
" (judges_teams_id,judges_timeslots_id,projects_id,year) ".
|
|
" VALUES ('{$jteam[$jteam_id]['team_id']}', ".
|
|
" '{$available_timeslots[$y]['id']}', ".
|
|
" '$pid', '{$config['FAIRYEAR']}')");
|
|
|
|
}
|
|
TRACE("\n");
|
|
}
|
|
|
|
}
|
|
|
|
TRACE("All Done.\n");
|
|
echo "</pre>";
|
|
|
|
set_percent(-1);
|
|
set_status("Done");
|
|
|
|
//echo happy("Scheduler completed successfully");
|
|
|
|
//send_footer();
|
|
?>
|