forked from science-ation/science-ation
Use the annealer to assign projects and judges to timeslots. This should
prevent a project from getting too many judges in a row, and also a judge from judging 2 projects at the same time (which would be more of a problem). Still need to write a verifier to check all the results. But the annealer tests look pretty good. Unfortunately it takes a pretty long time to run. :(
This commit is contained in:
parent
b86bb3bc9a
commit
03acd9d8fc
@ -8,6 +8,7 @@ class annealer {
|
|||||||
var $cost;
|
var $cost;
|
||||||
var $start_temp, $start_moves;
|
var $start_temp, $start_moves;
|
||||||
var $cost_function_callback;
|
var $cost_function_callback;
|
||||||
|
var $pick_move_callback;
|
||||||
var $iterations;
|
var $iterations;
|
||||||
var $items_per_bucket;
|
var $items_per_bucket;
|
||||||
var $rate;
|
var $rate;
|
||||||
@ -19,6 +20,7 @@ class annealer {
|
|||||||
$this->start_temp = $start_temp;
|
$this->start_temp = $start_temp;
|
||||||
$this->start_moves = $start_moves;
|
$this->start_moves = $start_moves;
|
||||||
$this->cost_function_callback = $cost_function_cb;
|
$this->cost_function_callback = $cost_function_cb;
|
||||||
|
unset($this->pick_move_callback);
|
||||||
|
|
||||||
$this->bucket_cost = array();
|
$this->bucket_cost = array();
|
||||||
$this->bucket = array();
|
$this->bucket = array();
|
||||||
@ -46,6 +48,11 @@ class annealer {
|
|||||||
"Cost={$this->cost}\n");
|
"Cost={$this->cost}\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function set_pick_move($func)
|
||||||
|
{
|
||||||
|
$this->pick_move_callback = $func;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function pick_move()
|
function pick_move()
|
||||||
{
|
{
|
||||||
@ -81,6 +88,10 @@ class annealer {
|
|||||||
{
|
{
|
||||||
list($b1, $i1, $b2, $i2) = $move;
|
list($b1, $i1, $b2, $i2) = $move;
|
||||||
|
|
||||||
|
if($b1 == $b2) {
|
||||||
|
return $this->compute_delta_cost_same_bucket($move);
|
||||||
|
}
|
||||||
|
|
||||||
$cost = 0;
|
$cost = 0;
|
||||||
|
|
||||||
$b1_old = $this->bucket[$b1];
|
$b1_old = $this->bucket[$b1];
|
||||||
@ -130,20 +141,53 @@ class annealer {
|
|||||||
return array($cost, array($c1, $b1_new, $c2, $b2_new));
|
return array($cost, array($c1, $b1_new, $c2, $b2_new));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function compute_delta_cost_same_bucket($move)
|
||||||
|
{
|
||||||
|
list($b1, $i1, $b2, $i2) = $move;
|
||||||
|
|
||||||
|
$cost = 0;
|
||||||
|
|
||||||
|
$b_old = $this->bucket[$b1];
|
||||||
|
|
||||||
|
$b_new = array();
|
||||||
|
/* Make a new bucket list */
|
||||||
|
for($x=0; $x<count($b_old); $x++) {
|
||||||
|
if($x == $i1) {
|
||||||
|
/* Swap or remove this index */
|
||||||
|
if($i2 != -1) $b_new[] = $b_old[$i2];
|
||||||
|
} else if($x == $i2) {
|
||||||
|
$b_new[] = $b_old[$i1];
|
||||||
|
} else {
|
||||||
|
$b_new[] = $b_old[$x];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Assign the new item lists to the buckets */
|
||||||
|
$this->bucket[$b1] = $b_new;
|
||||||
|
|
||||||
|
/* Compute costs */
|
||||||
|
$cost -= $this->bucket_cost[$b1];
|
||||||
|
|
||||||
|
$c1 = $this->cost_function($b1);
|
||||||
|
$cost += $c1;
|
||||||
|
|
||||||
|
/* Return to the original bucket lists */
|
||||||
|
$this->bucket[$b1] = $b_old;
|
||||||
|
|
||||||
|
return array($cost, array($c1, $b_new, 0, array()));
|
||||||
|
}
|
||||||
|
|
||||||
function accept_move($move, $movedata)
|
function accept_move($move, $movedata)
|
||||||
{
|
{
|
||||||
list($b1, $i1, $b2, $i2) = $move;
|
list($b1, $i1, $b2, $i2) = $move;
|
||||||
list($c1, $b1_new, $c2, $b2_new) = $movedata;
|
list($c1, $b1_new, $c2, $b2_new) = $movedata;
|
||||||
// TRACE("Old buckets:\n");
|
|
||||||
// $this->print_bucket($b1);
|
|
||||||
// $this->print_bucket($b2);
|
|
||||||
$this->bucket[$b1] = $b1_new;
|
$this->bucket[$b1] = $b1_new;
|
||||||
$this->bucket[$b2] = $b2_new;
|
|
||||||
// TRACE("New buckets:\n");
|
|
||||||
// $this->print_bucket($b1);
|
|
||||||
// $this->print_bucket($b2);
|
|
||||||
$this->bucket_cost[$b1] = $c1;
|
$this->bucket_cost[$b1] = $c1;
|
||||||
$this->bucket_cost[$b2] = $c2;
|
if($b1 != $b2) {
|
||||||
|
$this->bucket[$b2] = $b2_new;
|
||||||
|
$this->bucket_cost[$b2] = $c2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function anneal()
|
function anneal()
|
||||||
@ -153,17 +197,22 @@ class annealer {
|
|||||||
$last_cost = 0;
|
$last_cost = 0;
|
||||||
$last_cost_count = 0;
|
$last_cost_count = 0;
|
||||||
|
|
||||||
if($this->num_buckets <= 1) {
|
// if($this->num_buckets <= 1) {
|
||||||
TRACE("Only one Bucket, nothing to anneal.\n");
|
// TRACE("Only one Bucket, nothing to anneal.\n");
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
// $this->print_buckets();
|
// $this->print_buckets();
|
||||||
while(1) {
|
while(1) {
|
||||||
$moves = $this->start_moves;
|
$moves = $this->start_moves;
|
||||||
for($m = 0; $m<$moves; $m++) {
|
for($m = 0; $m<$moves; $m++) {
|
||||||
// $this->print_buckets();
|
// $this->print_buckets();
|
||||||
/* Pick 2 moves at random */
|
/* Pick 2 moves at random */
|
||||||
$move = $this->pick_move();
|
if(isset ($this->pick_move_callback)) {
|
||||||
|
$cb = $this->pick_move_callback;
|
||||||
|
$move = $cb($this);
|
||||||
|
} else {
|
||||||
|
$move = $this->pick_move();
|
||||||
|
}
|
||||||
/* See what the new cost is compared to the old */
|
/* See what the new cost is compared to the old */
|
||||||
list($delta_c, $movedata) =
|
list($delta_c, $movedata) =
|
||||||
$this->compute_delta_cost($move);
|
$this->compute_delta_cost($move);
|
||||||
@ -178,7 +227,7 @@ class annealer {
|
|||||||
$this->accept_move($move, $movedata);
|
$this->accept_move($move, $movedata);
|
||||||
$current_cost += $delta_c;
|
$current_cost += $delta_c;
|
||||||
$n_accepted++;
|
$n_accepted++;
|
||||||
if($current_cost < $this->cost)
|
// if($current_cost < $this->cost)
|
||||||
$this->cost = $current_cost;
|
$this->cost = $current_cost;
|
||||||
// TRACE("Move accepted, cost=$current_cost\n");
|
// TRACE("Move accepted, cost=$current_cost\n");
|
||||||
} else {
|
} else {
|
||||||
|
@ -312,9 +312,12 @@ for($k=0; $k<count($keys); $k++) {
|
|||||||
jdiv_compute_cost, $project_ids);
|
jdiv_compute_cost, $project_ids);
|
||||||
$a->anneal();
|
$a->anneal();
|
||||||
|
|
||||||
|
$jdiv[$jdiv_id]['jteams'] = array();
|
||||||
for($x=0;$x<$a->num_buckets; $x++) {
|
for($x=0;$x<$a->num_buckets; $x++) {
|
||||||
$bkt = $a->bucket[$x];
|
$bkt = $a->bucket[$x];
|
||||||
TRACE(" SubTeam $x:\n");
|
TRACE(" SubTeam $x:\n");
|
||||||
|
$jdiv[$jdiv_id]['jteams'][] = $jteam_id;
|
||||||
|
|
||||||
$jteam[$jteam_id]['id'] = $jteam_id;
|
$jteam[$jteam_id]['id'] = $jteam_id;
|
||||||
$jteam[$jteam_id]['projects'] = $a->bucket[$x];
|
$jteam[$jteam_id]['projects'] = $a->bucket[$x];
|
||||||
$jteam[$jteam_id]['sub'] = $x;
|
$jteam[$jteam_id]['sub'] = $x;
|
||||||
@ -350,7 +353,7 @@ $willing_chair_question_id = questions_find_question_id("judgereg", $config['FAI
|
|||||||
printf("Judge Willing Chair = Question ID $willing_chair_question_id\n");
|
printf("Judge Willing Chair = Question ID $willing_chair_question_id\n");
|
||||||
|
|
||||||
/* Clean out the judging teams that were autocreated */
|
/* Clean out the judging teams that were autocreated */
|
||||||
TRACE("Deleting existing judging teams:");
|
TRACE("Deleting existing divisional judging teams:");
|
||||||
$q = mysql_query("SELECT * FROM judges_teams WHERE autocreate_type_id=1 AND year={$config['FAIRYEAR']}");
|
$q = mysql_query("SELECT * FROM judges_teams WHERE autocreate_type_id=1 AND year={$config['FAIRYEAR']}");
|
||||||
while($r = mysql_fetch_object($q)) {
|
while($r = mysql_fetch_object($q)) {
|
||||||
$jteam_id = $r->id;
|
$jteam_id = $r->id;
|
||||||
@ -582,29 +585,178 @@ while($r=mysql_fetch_object($q)) {
|
|||||||
$x++;
|
$x++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$n_timeslots = count($available_timeslots);
|
||||||
|
|
||||||
TRACE("Assigning Judging Teams and Projects to Timeslots...\n");
|
|
||||||
for($x=1;$x<count($jteam); $x++) {
|
|
||||||
$pids = $jteam[$x]['projects'];
|
|
||||||
TRACE(" Judging Team $x: ");
|
|
||||||
for($y=0;$y<count($pids); $y++) {
|
|
||||||
$pid = $pids[$y];
|
|
||||||
|
|
||||||
TRACE(($y+1).":$pid ");
|
$project_index = array();
|
||||||
|
function timeslot_pick_move($a)
|
||||||
|
{
|
||||||
|
global $n_timeslots;
|
||||||
|
|
||||||
mysql_query("INSERT INTO judges_teams_timeslots_link ".
|
/* Bucket id always 0 */
|
||||||
|
$b1 = 0;
|
||||||
|
$i1 = rand(0, count($a->bucket[0]) - 1);
|
||||||
|
|
||||||
|
/* Find the start and end of this project timeslots */
|
||||||
|
$i_s = floor($i1 / $n_timeslots) * $n_timeslots;
|
||||||
|
$i_e = $i_s + $n_timeslots - 1;
|
||||||
|
|
||||||
|
/* THe second number must be between $i_s and $i_e */
|
||||||
|
$b2 = 0;
|
||||||
|
while(1) {
|
||||||
|
$i2 = rand($i_s, $i_e);
|
||||||
|
if($i2 != $i1) break;
|
||||||
|
}
|
||||||
|
// TRACE("Move=($b1, $i1, $b2, $i2)\n");
|
||||||
|
return array($b1, $i1, $b2, $i2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Computes the cost of everything, since we slice the data two
|
||||||
|
* ways we can't really use delta costs
|
||||||
|
* In this, the bucket_id will always be 1, because we don't
|
||||||
|
* actually use buckets */
|
||||||
|
function timeslot_cost_function($annealer, $bucket_id, $ids)
|
||||||
|
{
|
||||||
|
global $project_index;
|
||||||
|
global $jteam;
|
||||||
|
global $n_timeslots;
|
||||||
|
|
||||||
|
$cost = 0;
|
||||||
|
|
||||||
|
// print("Count $ids=".count($ids));
|
||||||
|
|
||||||
|
/* The pick_move function ensures that projects can't
|
||||||
|
* move out of a jteam so we dont' need to check that */
|
||||||
|
|
||||||
|
|
||||||
|
/* 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 */
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check to make sure a judge isn't judging two projects
|
||||||
|
* at the same time */
|
||||||
|
$n_pids = count($project_index);
|
||||||
|
for($offset = 0; $offset < $n_timeslots; $offset++) {
|
||||||
|
for($x=0; $x<$n_pids-1; $x++) {
|
||||||
|
$o1 = $x * ($n_timeslots) + $offset;
|
||||||
|
$jteam_id1 = $ids[$o1];
|
||||||
|
if($jteam_id1 == 0) continue;
|
||||||
|
for($y=$x+1; $y<$n_pids; $y++) {
|
||||||
|
$o2 = $y * ($n_timeslots) + $offset;
|
||||||
|
$jteam_id2 = $ids[$o2];
|
||||||
|
if($jteam_id1 == $jteam_id2)
|
||||||
|
$cost += 50;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $cost;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$keys = array_keys($jdiv);
|
||||||
|
for($k=0; $k<count($keys); $k++) {
|
||||||
|
$jdiv_id = $keys[$k];
|
||||||
|
$pids = array_keys($jdiv[$jdiv_id]['projects']);
|
||||||
|
|
||||||
|
unset($project_rlookup);
|
||||||
|
$project_rlookup = array();
|
||||||
|
|
||||||
|
for($x=0; $x<count($pids); $x++) {
|
||||||
|
$project_rlookup[$pids[$x]] = $x;
|
||||||
|
}
|
||||||
|
|
||||||
|
$project_index = $pids;
|
||||||
|
|
||||||
|
$current_jdiv = $jdiv_id;
|
||||||
|
|
||||||
|
printf(count($pids). " projects in this jdiv\n");
|
||||||
|
unset($jteams_ids);
|
||||||
|
$jteams_ids = array();
|
||||||
|
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, then pad with 0 up
|
||||||
|
* to a length of num_project * n_timeslots */
|
||||||
|
$jteams = $jdiv[$jdiv_id]['jteams'];
|
||||||
|
foreach($jteams as $jteam_id) {
|
||||||
|
$p = $jteam[$jteam_id]['projects'];
|
||||||
|
for($y=0;$y<count($jteam[$jteam_id]['projects']); $y++) {
|
||||||
|
$pid = $jteam[$jteam_id]['projects'][$y];
|
||||||
|
$idx = $project_rlookup[$pid];
|
||||||
|
|
||||||
|
$o = $n_timeslots * $idx;
|
||||||
|
for($o = $n_timeslots * $idx; ; $o++) {
|
||||||
|
if($jteams_ids[$o] != 0) continue;
|
||||||
|
|
||||||
|
$jteams_ids[$o] = $jteam_id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print("Jteams ids len=".count($jteams_ids));
|
||||||
|
print("\n");
|
||||||
|
|
||||||
|
$e = 500 + 50 * ($data['effort'] / 1000);
|
||||||
|
$a = new annealer(1, 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++) {
|
||||||
|
$idx = ($x * $n_timeslots) + $y;
|
||||||
|
$jteam_id = $a->bucket[0][$idx];
|
||||||
|
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)".
|
" (judges_teams_id,judges_timeslots_id,year)".
|
||||||
" VALUES ('{$jteam[$x]['team_id']}', ".
|
" VALUES ('{$jteam[$jteam_id]['team_id']}', ".
|
||||||
" '{$available_timeslots[$y]['id']}', ".
|
" '{$available_timeslots[$y]['id']}', ".
|
||||||
" '{$config['FAIRYEAR']}')");
|
" '{$config['FAIRYEAR']}')");
|
||||||
|
|
||||||
mysql_query("INSERT INTO judges_teams_timeslots_projects_link ".
|
mysql_query("INSERT INTO judges_teams_timeslots_projects_link ".
|
||||||
" (judges_teams_id,judges_timeslots_id,projects_id,year) ".
|
" (judges_teams_id,judges_timeslots_id,projects_id,year) ".
|
||||||
" VALUES ('{$jteam[$x]['team_id']}', ".
|
" VALUES ('{$jteam[$jteam_id]['team_id']}', ".
|
||||||
" '{$available_timeslots[$y]['id']}', ".
|
" '{$available_timeslots[$y]['id']}', ".
|
||||||
" '$pid', '{$config['FAIRYEAR']}')");
|
" '$pid', '{$config['FAIRYEAR']}')");
|
||||||
|
|
||||||
|
}
|
||||||
|
TRACE("\n");
|
||||||
}
|
}
|
||||||
print("\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TRACE("All Done.\n");
|
TRACE("All Done.\n");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user