science-ation/admin/judges_sa.php

574 lines
15 KiB
PHP
Raw Normal View History

<?
/*
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");
auth_required('admin');
function TRACE()
{
}
//function TRACE($str) { print($str); }
function TRACE_R()
{
}
/* Given the team data, pick a judge, then pick a new team
* for them */
function pick_random_move(&$team)
{
//TRACE_R($team);
/* Pick 2 random teams*/
$tms = count($team);
while(1) {
$t1 = rand(0, $tms - 1);
if(count($team[$t1]['judges']) > 0) break;
}
$t2 = rand(0, $tms - 2);
if($t2 >= $t1) $t2++;
/* Pick a judge on team1 */
$j1 = rand(0, count($team[$t1]['judges']) - 1);
/* Pick a judge or the empty slot on team2 */
$j2 = rand(0, count($team[$t2]['judges']));
if($j2 == count($team[$t2]['judges'])) {
$j2 = -1;
}
TRACE("Random move: ($t1,$j1) ($t2,$j2)\n");
TRACE_R($team[$t1]['judges']);
TRACE("T2:\n");
TRACE_R($team[$t2]['judges']);
TRACE("\n");
/* The move is team1,judge1 <==> team2,judge2 */
return array($t1, $j1, $t2, $j2);
}
/* The cost function is:
+ 200 * each judge below the min for each team
+ 100 * each judge above the max for each team
+ -20 * each level of preference away from the
max level for each judge
( ex: if a judge has selected LS->2, PS->0, CS->-1
then matching that judge with a:
LS = -40,
PS = 0,
CS = -20,
else = 0
)
*/
/* Compute the cost of adding a judge to a team */
function compute_team_cost(&$teams, &$judges, $team_id)
{
$cost = 0;
$t =& $teams[$team_id];
/* Compute the over max / under min costs */
$c = count($t['judges']);
$min = ($c < $t['min']) ? $t['min'] - $c : 0;
$max = ($c > $t['max']) ? $c - $t['max'] : 0;
$cost += $min * 20;
$cost += $max * 10;
// TRACE("Under min=$min, over max=$max\n");
/* For each judge on the team, score their preferences */
reset($t['judges']);
while( list($key, $judge_id) = each($t['judges']) ) {
$j = $judges[$judge_id];
/* Get the division, and see where it fits with this
* judges preferences */
$dpref = $j['divprefs'][$t['division']];
$cpref = $j['catprefs'][$t['category']];
// TRACE("Judge $judge_id cp=$cpref, dp=$dpref\n");
$cost += 2 * (-$dpref + 2);
$cost += 2 * (-$cpref + 2);
}
TRACE("Team $team_id, cost is $cost\n");
return $cost;
}
function compute_delta_cost(&$teams, &$judges, $move)
{
list($tid1, $jidx1, $tid2, $jidx2) = $move;
$t1 =& $teams[$tid1];
$t2 =& $teams[$tid2];
$ja1 = $t1['judges'];
$ja2 = $t2['judges'];
/* Turn the indexes into judge IDs */
$jid1 = $t1['judges'][$jidx1];
if($jidx2 == -1)
$jid2 = -1;
else
$jid2 = $t2['judges'][$jidx2];
$cost = 0;
/* Make new arrays for each judge list */
$nj1 = array();
for($x=0; $x<count($t1['judges']); $x++) {
$id = $t1['judges'][$x];
if($x == $jidx1) {
/* This is the index of the judge we're
* supposed to swap (or remove) */
if($jid2 != -1) $nj1[] = $jid2;
} else {
$nj1[] = $id;
}
}
$t1['judges'] = $nj1;
$nj2 = array();
for($x=0; $x<count($t2['judges']); $x++) {
$id = $t2['judges'][$x];
if($x == $jidx2) {
/* if jidx2 is -1 we'll never get here, else
* we might, meaning that this is where
* we want to swap in the value from the first
* array */
$nj2[] = $jid1;
} else {
$nj2[] = $id;
}
}
/* jidx2 may be -1 meaning that something was removed
* from the first array, but not added back to this one,
* do that now so we don't lose judges */
if($jidx2 == -1) $nj2[] = $jid1;
$t2['judges'] = $nj2;
/* Recompute the costs */
$cost -= $t1['cost'];
$cost -= $t2['cost'];
$c1 = compute_team_cost($teams, $judges, $tid1);
$c2 = compute_team_cost($teams, $judges, $tid2);
$cost += $c1 + $c2;
TRACE("Team $tid1 cost {$t1['cost']} -> $c1\n");
TRACE("Team $tid2 cost {$t2['cost']} -> $c2\n");
TRACE("Delta = $cost\n");
$t1['judges'] = $ja1;
$t2['judges'] = $ja2;
return array($cost, array($nj1, $c1, $nj2, $c2)) ;
}
function record_move(&$teams, $move, $movedata)
{
list($tid1, $jidx1, $tid2, $jidx2) = $move;
list($judges1, $c1, $judges2, $c2) = $movedata;
$t1 =& $teams[$tid1];
$t2 =& $teams[$tid2];
// TRACE_R($t1);
$t1['judges'] = $judges1;
$t1['cost'] = $c1;
$t2['judges'] = $judges2;
$t2['cost'] = $c2;
TRACE("T1:");
TRACE_R($t1['judges']);
TRACE("\nT2:");
TRACE_R($t2['judges']);
TRACE("\n");
// TRACE_R($t1);
// TRACE_R($t2);
}
/* Inputs to annealer:
* - data['min_per_team']
* - 'max_per_team'
* - 'teams' [division][category] = number of teams
*
* - judges
*/
function judges_assign_anneal($divisions, $categories, $languages, $judges, &$team, $data)
{
$num_teams = count($team);
$x=0;
TRACE("Input: $num_teams juding teams \n");
if($num_teams <= 0) return;
/* Inital assignment of judges to teams */
reset($judges);
while( list($j, $ji) = each($judges)) {
$team[$x % $num_teams]['judges'][] = $j;
$x++;
}
/* Compute inital costs */
$current_cost = 0;
// reset($team);
for($x=0; $x<$num_teams; $x++) {
$t =& $team[$x];
$t['cost'] = compute_team_cost($team, $judges, $x);
$current_cost += $t['cost'];
}
/* Anneal */
$temperature = 25.0;
while(1) {
$moves = 500;
for($m = 0; $m<$moves; $m++) {
/* Pick 2 moves at random */
$move = pick_random_move($team);
/* See what the new cost is compared to the old */
list($delta_c, $movedata) =
compute_delta_cost($team, $judges, $move);
$r = floatval(rand()) / floatval(getrandmax());
/* Decide if we want to keep it */
$e = exp(-$delta_c / $temperature);
TRACE("r=$r, exp=$e\n");
if($r < exp(-$delta_c / $temperature)) {
/* Yes, we do, record the move */
record_move($team, $move, $movedata);
$current_cost += $delta_c;
$n_accepted++;
if($current_cost < $best_cost)
$best_cost = $current_cost;
TRACE("Move accepted, cost=$current_cost\n");
} else {
TRACE("Move rejected\n");
}
}
TRACE("Cost is $current_cost\n");
$temperature *= 0.9;
if($temperature <= 0.05) break;
}
}
$q=mysql_query("SELECT * FROM projectdivisions WHERE year='".$config['FAIRYEAR']."' ORDER BY id");
while($r=mysql_fetch_object($q))
$div[$r->id]=$r->division;
$q=mysql_query("SELECT * FROM projectcategories WHERE year='".$config['FAIRYEAR']."' ORDER BY id");
while($r=mysql_fetch_object($q))
$cat[$r->id]=$r->category;
$langr = array();
$q=mysql_query("SELECT * FROM languages WHERE active='Y'");
while($r=mysql_fetch_object($q))
$langr[] = $r->lang;
$configq=mysql_query("SELECT * FROM judges_schedulerconfig WHERE year='".$config['FAIRYEAR']."'");
$data=array();
while($configr=mysql_fetch_object($configq))
$data[$configr->var]=$configr->val;
$data['teams'] = array();
$data['projects'] = array();
$jdivisions = array();
/* Load a list of all projects, and build an array of projects in each
* category */
// print_r($langr);
$q=mysql_query("SELECT * FROM projects WHERE year='".$config['FAIRYEAR']."'");
while($r=mysql_fetch_object($q)) {
$d_id = $r->projectdivisions_id;
$c_id = $r->projectcategories_id;
$l_id = $r->language;
$data['projects'][$r->id]['timetable'] = array();
if(!in_array($l_id, $langr)) $l_id = 'en';
$jdivisions[$d_id][$c_id][$l_id][] = $r->id;
TRACE("Found project id {$r->id} in $d_id, $c_id, $l_id\n\n");
}
$t=0;
$max_ts = 0;
foreach($div AS $d_id=>$d_val)
{
foreach($cat AS $c_id=>$c_val)
{
foreach($langr AS $l_id) {
$num = count($jdivisions[$d_id][$c_id][$l_id]);
if($num <= 0) continue;
$numteams=ceil($num/$data['max_projects_per_team']*$data['num_times_judged']);
if($numteams < $data['num_times_judged'])
$numteams = $data['num_times_judged'];
TRACE("Judging teams for $d_id, $c_id, $l_id is $numteams\n\n");
$start_t = $t;
for($x=0; $x<$numteams; $x++) {
$team[$t]['division'] = $d_id;
$team[$t]['category'] = $c_id;
$team[$t]['language'] = $l_id;
$team[$t]['min'] = $data['min_judges_per_team'];
$team[$t]['max'] = $data['max_judges_per_team'];
$team[$t]['judges'] = array();
$t++;
}
TRACE("Created judging teams $start_t -> ".($t - 1)."\n\n");
TRACE("Need to assign these teams to $num projects: ");
// print_r($jdivisions[$d_id][$c_id][$l_id]);
TRACE("\n\n");
/* We just created teams $start_t -> $t-1, now we can assign which projects
* they judge in different timeslots */
/* Each project must be judged $data['num_times_judged'], and each team is
* allowed to judge $data['num_times_judged'] projects */
$x=0; /* Cycles over 0 -> $num */
$ts=1; /* Current timeslot , increment when all judging teams are assinged */
$j=0; /* Cycles over o0 -> $numteams */
$teams_at_this_ts = array();
$num_done = 0;
while(1) {
TRACE("x=$x ");
if($x == $num) $x=0;
/* Get the project id we want to look at */
$p = $jdivisions[$d_id][$c_id][$l_id][$x];
TRACE(" project=$p \n\n");
TRACE("This projects has ".(count($data['projects'][$p]['timetable']))." judging teams\n");
/* See if this project needs more judges */
if(count($data['projects'][$p]['timetable']) == $data['num_times_judged']) {
/* No, this project doesn't need any more judging teams */
TRACE(" This project doesn't need more teams, skipping\n\n");
// print_r($data['projects'][$p]['timetable']);
TRACE("\n\n");
if($data['projects'][$p]['timetable_done'] != 1) {
$data['projects'][$p]['timetable_done'] = 1;
$num_done++;
if($num_done == $num) break;
}
$x++;
continue;
}
/* Find a judging team to assign */
TRACE("Starting at team=$j\n\n");
while(1) {
TRACE("j=$j");
if($j == $numteams) $j = 0;
$jteam = $j + $start_t;
TRACE(" team=$jteam\n\n");
if(in_array($jteam, $teams_at_this_ts)) {
$j++;
continue;
}
if(!in_array($jteam, $data['projects'][$p]['timetable'])) {
/* Add this juding team to the timetable */
TRACE("Project $p, timeslot $ts = judging team $jteam\n\n");
$data['projects'][$p]['timetable'][$ts] = $jteam;
$teams_at_this_ts[] = $jteam;
$j++;
break;
}
$j++;
if($j==$numteams) $j=0;
}
$x++;
if(count($teams_at_this_ts) == $numteams) {
$ts++;
$teams_at_this_ts = array();
if($ts > $max_ts) $max_ts = $ts;
}
}
}
}
}
//print_r($data['projects']);
TRACE("Teams: ".count($team)."\n");
//print_r($team);
TRACE("\n");
$q=mysql_query("SELECT judges.* FROM judges,judges_years WHERE complete='yes' AND judges_years.year='{$config['FAIRYEAR']}' AND judges_years.judges_id=judges.id");
$judges=array();
while($r=mysql_fetch_object($q))
{
unset($divprefs);
unset($catprefs);
//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;
$judges[]=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"=>$r->willing_chair,
"divprefs"=>$divprefs,
"catprefs"=>$catprefs
);
}
//print_r($judges);
//echo nl2br(TRACE_R($judges, true));
judges_assign_anneal($div,$cat, $langr, $judges, $team, $data);
//print_r( $team);
$teamnums=array();
send_header("Judging teams automatic scheduler");
echo i18n("Judging teams successfully created. You can review the teams and modify as desired using the following links");
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 />";
print("Project Timeslots:<br>\n");
print("<table border=1><tr><td>Project ID</td>");
for($x=0;$x<$max_ts;$x++) {
print("<td>Slot ".($x + 1)."</td>");
}
print("</tr>");
while(list($proj_id, $projinfo) = each( $data['projects'] )) {
print("<tr><td>$proj_id</td>");
$last_slot = 1;
while(list($slot,$jteam) = each ($projinfo['timetable']) ) {
while($last_slot != $slot) {
print("<td>&nbsp;</td>");
$last_slot++;
}
print("<td>".($jteam+1)."</td>");
$last_slot++;
}
print("</tr>");
}
print("</table>");
$totalcost=0;
while(list($tn, $t) = each($team)) {
//team numbers start with 0 in the annealer, but we want them to start at 1, so just tn++ here.
$tn++;
print("Team $tn: ({$div[$t['division']]}({$t['division']}),{$cat[$t['category']]}({$t['category']})) ".
"(cost:{$t['cost']} )<br>\n");
$totalcost+=$t['cost'];
if(!$teamnums[$t['division']][$t['category']]) $teamnums[$t['division']][$t['category']]=1;
else $teamnums[$t['division']][$t['category']]++;
mysql_query("INSERT INTO judges_teams (num,name,autocreate_type_id,year) VALUES ('$tn','(Divisional) {$div[$t['division']]} - {$cat[$t['category']]} #{$teamnums[$t['division']][$t['category']]}','1','{$config['FAIRYEAR']}')");
$team_id=mysql_insert_id();
while(list($key, $j) = each($t['judges']) ) {
$judge = $judges[$j];
print("&nbsp;&nbsp;&nbsp;{$judge['name']}<br>\n");
mysql_query("INSERT INTO judges_teams_link (judges_id,judges_teams_id,captain,year) VALUES ('{$judge['judges_id']}','$team_id','{$judge['willing_chair']}','{$config['FAIRYEAR']}')");
}
//and finally, link the team to the award
$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='{$t['category']}'
AND award_awards_projectdivisions.projectdivisions_id='{$t['division']}'
AND award_awards.award_types_id='1'
");
if(mysql_num_rows($q)!=1)
{
echo error(i18n("Cannot find award for %1 - %2",array($cat[$t['category']],$div[$t['division']])));
}
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']}')");
}
}
echo "<br />";
echo "<br />";
echo "<b>Total 'cost' for all teams: $totalcost</b>";
send_footer();
?>