forked from science-ation/science-ation
456 lines
12 KiB
PHP
456 lines
12 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");
|
||
|
auth_required('admin');
|
||
|
|
||
|
function TRACE()
|
||
|
{
|
||
|
}
|
||
|
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)<br>");
|
||
|
TRACE_R($team[$t1]['judges']);
|
||
|
TRACE("<br>T2:");
|
||
|
TRACE_R($team[$t2]['judges']);
|
||
|
TRACE("<br>");
|
||
|
/* 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<br>");
|
||
|
|
||
|
/* 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<br>");
|
||
|
|
||
|
$cost += 2 * (-$dpref + 2);
|
||
|
$cost += 2 * (-$cpref + 2);
|
||
|
}
|
||
|
|
||
|
TRACE("Team $team_id, cost is $cost<br>");
|
||
|
|
||
|
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<br>");
|
||
|
TRACE("Team $tid2 cost {$t2['cost']} -> $c2<br>");
|
||
|
TRACE("Delta = $cost<br>");
|
||
|
|
||
|
$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("<br>T2:");
|
||
|
TRACE_R($t2['judges']);
|
||
|
TRACE("<br>");
|
||
|
|
||
|
// 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, $judges, $data)
|
||
|
{
|
||
|
/* Create an array to hold the team data */
|
||
|
$team = array();
|
||
|
|
||
|
|
||
|
$t=0;
|
||
|
reset($data['teams']);
|
||
|
while( list($div, $c) = each($data['teams']) ){
|
||
|
reset($c);
|
||
|
while( list($cat, $num) = each($c) ) {
|
||
|
for($x=0; $x<$num; $x++) {
|
||
|
$team[$t]['division'] = $div;
|
||
|
$team[$t]['category'] = $cat;
|
||
|
$team[$t]['min'] = $data['min_judges_per_team'];
|
||
|
$team[$t]['max'] = $data['max_judges_per_team'];
|
||
|
$team[$t]['judges'] = array();
|
||
|
$t++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$num_teams = $t;
|
||
|
$x=0;
|
||
|
/* 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<count($team); $x++) {
|
||
|
$t =& $team[$x];
|
||
|
$t['cost'] = compute_team_cost($team, $judges, $x);
|
||
|
$current_cost += $t['cost'];
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/* Anneal */
|
||
|
$temperature = 25.0;
|
||
|
while(1) {
|
||
|
$moves = 1000;
|
||
|
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<br>");
|
||
|
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<br>");
|
||
|
} else {
|
||
|
TRACE("Move rejected<br>");
|
||
|
}
|
||
|
|
||
|
}
|
||
|
TRACE("Cost is $current_cost<br>");
|
||
|
$temperature *= 0.9;
|
||
|
|
||
|
if($temperature <= 0.05) break;
|
||
|
}
|
||
|
|
||
|
|
||
|
return $team;
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
$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;
|
||
|
|
||
|
|
||
|
$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();
|
||
|
|
||
|
foreach($div AS $d_id=>$d_val)
|
||
|
{
|
||
|
foreach($cat AS $c_id=>$c_val)
|
||
|
{
|
||
|
$numq=mysql_query("SELECT COUNT(id) AS num FROM projects WHERE projectcategories_id='$c_id' AND projectdivisions_id='$d_id'");
|
||
|
$numr=mysql_fetch_object($numq);
|
||
|
$numteams=ceil($numr->num/$data['max_projects_per_team']*$data['num_times_judged']);
|
||
|
$data['teams'][$d_id][$c_id]=$numteams;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
$data = array( 'min_per_team' => 2,
|
||
|
'max_per_team' => 4,
|
||
|
'teams' => array() );
|
||
|
$data['teams']['S']['LS'] = 4;
|
||
|
$data['teams']['S']['PS'] = 4;
|
||
|
$data['teams']['S']['Case'] = 3;
|
||
|
$data['teams']['S']['CS'] = 2;
|
||
|
$data['teams']['I']['LS'] = 3;
|
||
|
$data['teams']['I']['PS'] = 3;
|
||
|
$data['teams']['I']['Case'] = 2;
|
||
|
$data['teams']['I']['CS'] = 1;
|
||
|
$data['teams']['J']['LS'] = 2;
|
||
|
$data['teams']['J']['PS'] = 2;
|
||
|
$data['teams']['J']['Case'] = 1;
|
||
|
$data['teams']['J']['CS'] = 4;
|
||
|
*/
|
||
|
|
||
|
$q=mysql_query("SELECT * FROM judges ");
|
||
|
|
||
|
$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
|
||
|
);
|
||
|
}
|
||
|
|
||
|
//echo nl2br(TRACE_R($judges, true));
|
||
|
|
||
|
$teams = judges_assign_anneal($div,$cat, $judges, $data);
|
||
|
|
||
|
//TRACE_R( $teams);
|
||
|
$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 />";
|
||
|
|
||
|
$totalcost=0;
|
||
|
while(list($tn, $t) = each($teams)) {
|
||
|
//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>");
|
||
|
$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(" {$judge['name']}<br>");
|
||
|
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();
|
||
|
?>
|