forked from science-ation/science-ation
Update the annealer to recompute the cost of multiple affected buckets
on a move if necessary. Add the annealer to compute floor locations (but don't link it to anything yet because there's no editor, we're testing it with GVRSF this year).
This commit is contained in:
parent
89547f902a
commit
de761c8b3e
@ -1,19 +1,23 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
|
||||||
class annealer {
|
class annealer {
|
||||||
|
|
||||||
var $num_buckets;
|
var $num_buckets;
|
||||||
var $bucket;
|
var $bucket;
|
||||||
var $bucket_cost;
|
var $bucket_cost;
|
||||||
|
var $bucket_cost_new;
|
||||||
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 $pick_move_callback;
|
||||||
var $update_callback;
|
var $update_callback;
|
||||||
|
var $delta_cost_bucket_ids_callback;
|
||||||
var $iterations;
|
var $iterations;
|
||||||
var $items_per_bucket;
|
var $items_per_bucket;
|
||||||
var $max_items_per_bucket;
|
var $max_items_per_bucket;
|
||||||
var $rate;
|
var $rate;
|
||||||
|
var $move_bucket_ids;
|
||||||
|
|
||||||
function annealer($num_buckets, $start_temp, $start_moves, $rate,
|
function annealer($num_buckets, $start_temp, $start_moves, $rate,
|
||||||
$cost_function_cb, $items)
|
$cost_function_cb, $items)
|
||||||
@ -24,16 +28,19 @@ class annealer {
|
|||||||
$this->cost_function_callback = $cost_function_cb;
|
$this->cost_function_callback = $cost_function_cb;
|
||||||
unset($this->pick_move_callback);
|
unset($this->pick_move_callback);
|
||||||
unset($this->update_callback);
|
unset($this->update_callback);
|
||||||
|
unset($this->delta_cost_bucket_ids_callback);
|
||||||
|
|
||||||
$this->bucket_cost = array();
|
$this->bucket_cost = array();
|
||||||
|
$this->bucket_cost_old = array();
|
||||||
$this->bucket = array();
|
$this->bucket = array();
|
||||||
$this->iterations = 0;
|
$this->iterations = 0;
|
||||||
$this->items_per_bucket = count($items) / $num_buckets;
|
$this->items_per_bucket = ceil(count($items) / $num_buckets);
|
||||||
$this->rate = $rate;
|
$this->rate = $rate;
|
||||||
$this->max_items_per_bucket = 0;
|
$this->max_items_per_bucket = 0;
|
||||||
$ipb = ceil($this->items_per_bucket);
|
$ipb = ceil($this->items_per_bucket);
|
||||||
$i=0;
|
$i=0;
|
||||||
$this->cost = 0;
|
$this->cost = 0;
|
||||||
|
/* Assign to buckets */
|
||||||
for($x=0; $x<$num_buckets; $x++) {
|
for($x=0; $x<$num_buckets; $x++) {
|
||||||
unset($b);
|
unset($b);
|
||||||
$b = array();
|
$b = array();
|
||||||
@ -43,6 +50,10 @@ class annealer {
|
|||||||
$i++;
|
$i++;
|
||||||
}
|
}
|
||||||
$this->bucket[] = $b;
|
$this->bucket[] = $b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Then do costs after all bucket assignments are done */
|
||||||
|
for($x=0; $x<$num_buckets; $x++) {
|
||||||
$c = $this->cost_function($x);
|
$c = $this->cost_function($x);
|
||||||
$this->bucket_cost[] = $c;
|
$this->bucket_cost[] = $c;
|
||||||
$this->cost += $c;
|
$this->cost += $c;
|
||||||
@ -56,17 +67,21 @@ class annealer {
|
|||||||
{
|
{
|
||||||
$this->pick_move_callback = $func;
|
$this->pick_move_callback = $func;
|
||||||
}
|
}
|
||||||
|
|
||||||
function set_update_callback($func)
|
function set_update_callback($func)
|
||||||
{
|
{
|
||||||
$this->update_callback = $func;
|
$this->update_callback = $func;
|
||||||
}
|
}
|
||||||
|
function set_delta_cost_bucket_ids_callback($func)
|
||||||
|
{
|
||||||
|
$this->delta_cost_bucket_ids_callback = $func;
|
||||||
|
}
|
||||||
function set_max_items_per_bucket($num)
|
function set_max_items_per_bucket($num)
|
||||||
{
|
{
|
||||||
$this->max_items_per_bucket = $num;
|
$this->max_items_per_bucket = $num;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function pick_move()
|
function pick_move()
|
||||||
{
|
{
|
||||||
/* Pick a bucket and item */
|
/* Pick a bucket and item */
|
||||||
@ -109,38 +124,66 @@ class annealer {
|
|||||||
list($b1, $i1, $b2, $i2) = $move;
|
list($b1, $i1, $b2, $i2) = $move;
|
||||||
|
|
||||||
if($b1 == $b2) {
|
if($b1 == $b2) {
|
||||||
return $this->compute_delta_cost_same_bucket($move);
|
echo "Called on same bucket, not supported!\n";
|
||||||
|
exit;
|
||||||
|
// return $this->compute_delta_cost_same_bucket($move);
|
||||||
}
|
}
|
||||||
|
|
||||||
$cost = 0;
|
$cost = 0;
|
||||||
|
|
||||||
|
/* Save the old lists for easy restore */
|
||||||
$b1_old = $this->bucket[$b1];
|
$b1_old = $this->bucket[$b1];
|
||||||
$b2_old = $this->bucket[$b2];
|
$b2_old = $this->bucket[$b2];
|
||||||
|
|
||||||
if($i2 != -1) {
|
/* Setup new costs */
|
||||||
|
$this->bucket_cost_new = $this->bucket_cost;
|
||||||
|
|
||||||
|
/* Compute new lists with swapped elements */
|
||||||
|
if($i2 != -1) { /* Swap */
|
||||||
array_splice($this->bucket[$b1], $i1, 1, $b2_old[$i2]);
|
array_splice($this->bucket[$b1], $i1, 1, $b2_old[$i2]);
|
||||||
array_splice($this->bucket[$b2], $i2, 1, $b1_old[$i1]);
|
array_splice($this->bucket[$b2], $i2, 1, $b1_old[$i1]);
|
||||||
} else {
|
} else { /* Move one to other */
|
||||||
array_splice($this->bucket[$b1], $i1, 1);
|
array_splice($this->bucket[$b1], $i1, 1);
|
||||||
$this->bucket[$b2][] = $b1_old[$i1];
|
$this->bucket[$b2][] = $b1_old[$i1];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Compute costs */
|
/* Get the lists of buckets we need to recompute, by default
|
||||||
$cost -= $this->bucket_cost[$b1];
|
* just b1 and b2 */
|
||||||
$cost -= $this->bucket_cost[$b2];
|
if(isset ($this->delta_cost_bucket_ids_callback)) {
|
||||||
|
$cb = $this->delta_cost_bucket_ids_callback;
|
||||||
|
$ids = $cb($this, $b1);
|
||||||
|
$ids = array_unique(array_merge($ids, $cb($this, $b2)), SORT_NUMERIC );
|
||||||
|
} else {
|
||||||
|
$ids = array($b1, $b2);
|
||||||
|
}
|
||||||
|
|
||||||
$c1 = $this->cost_function($b1);
|
// TRACE("Recompute IDs:\n");
|
||||||
$c2 = $this->cost_function($b2);
|
// TRACE_R($ids);
|
||||||
|
|
||||||
$cost += $c1 + $c2;
|
|
||||||
|
/* Save that list */
|
||||||
|
$this->move_bucket_ids = $ids;
|
||||||
|
|
||||||
|
/* Compute a delta cost, recompute all costs for all buckets */
|
||||||
|
foreach($ids as $bucket_id) {
|
||||||
|
/* Compute costs */
|
||||||
|
$cost -= $this->bucket_cost[$bucket_id];
|
||||||
|
$this->bucket_cost_new[$bucket_id] = $this->cost_function($bucket_id);
|
||||||
|
$cost += $this->bucket_cost_new[$bucket_id];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Save the new lists */
|
||||||
|
$b1_new = $this->bucket[$b1];
|
||||||
|
$b2_new = $this->bucket[$b2];
|
||||||
|
|
||||||
/* Return to the original bucket lists */
|
/* Return to the original bucket lists */
|
||||||
$this->bucket[$b1] = $b1_old;
|
$this->bucket[$b1] = $b1_old;
|
||||||
$this->bucket[$b2] = $b2_old;
|
$this->bucket[$b2] = $b2_old;
|
||||||
|
|
||||||
return array($cost, array($c1, $b1_new, $c2, $b2_new));
|
return array($cost, array($b1_new, $b2_new));
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
function compute_delta_cost_same_bucket($move)
|
function compute_delta_cost_same_bucket($move)
|
||||||
{
|
{
|
||||||
list($b1, $i1, $b2, $i2) = $move;
|
list($b1, $i1, $b2, $i2) = $move;
|
||||||
@ -150,10 +193,10 @@ class annealer {
|
|||||||
$b_old = $this->bucket[$b1];
|
$b_old = $this->bucket[$b1];
|
||||||
|
|
||||||
$b_new = array();
|
$b_new = array();
|
||||||
/* Make a new bucket list */
|
/* Make a new bucket list
|
||||||
for($x=0; $x<count($b_old); $x++) {
|
for($x=0; $x<count($b_old); $x++) {
|
||||||
if($x == $i1) {
|
if($x == $i1) {
|
||||||
/* Swap or remove this index */
|
/* Swap or remove this index
|
||||||
if($i2 != -1) $b_new[] = $b_old[$i2];
|
if($i2 != -1) $b_new[] = $b_old[$i2];
|
||||||
} else if($x == $i2) {
|
} else if($x == $i2) {
|
||||||
$b_new[] = $b_old[$i1];
|
$b_new[] = $b_old[$i1];
|
||||||
@ -162,32 +205,31 @@ class annealer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Assign the new item lists to the buckets */
|
/* Assign the new item lists to the buckets
|
||||||
$this->bucket[$b1] = $b_new;
|
$this->bucket[$b1] = $b_new;
|
||||||
|
|
||||||
/* Compute costs */
|
/* Compute costs
|
||||||
$cost -= $this->bucket_cost[$b1];
|
$cost -= $this->bucket_cost[$b1];
|
||||||
|
|
||||||
$c1 = $this->cost_function($b1);
|
$c1 = $this->cost_function($b1);
|
||||||
$cost += $c1;
|
$cost += $c1;
|
||||||
|
|
||||||
/* Return to the original bucket lists */
|
/* Return to the original bucket lists
|
||||||
$this->bucket[$b1] = $b_old;
|
$this->bucket[$b1] = $b_old;
|
||||||
|
|
||||||
return array($cost, array($c1, $b_new, 0, array()));
|
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($b1_new, $b2_new) = $movedata;
|
||||||
|
|
||||||
$this->bucket[$b1] = $b1_new;
|
$this->bucket[$b1] = $b1_new;
|
||||||
$this->bucket_cost[$b1] = $c1;
|
if($b1 != $b2) $this->bucket[$b2] = $b2_new;
|
||||||
if($b1 != $b2) {
|
|
||||||
$this->bucket[$b2] = $b2_new;
|
$this->bucket_cost = $this->bucket_cost_new;
|
||||||
$this->bucket_cost[$b2] = $c2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function anneal()
|
function anneal()
|
||||||
@ -204,6 +246,7 @@ class annealer {
|
|||||||
// $this->print_buckets();
|
// $this->print_buckets();
|
||||||
$estimated_iterations = ceil(log(0.1 / $this->start_temp, $this->rate));
|
$estimated_iterations = ceil(log(0.1 / $this->start_temp, $this->rate));
|
||||||
|
|
||||||
|
// print_r($this);
|
||||||
$iterations = 0;
|
$iterations = 0;
|
||||||
while(1) {
|
while(1) {
|
||||||
$moves = $this->start_moves;
|
$moves = $this->start_moves;
|
||||||
|
652
admin/exhibithall_sa.php
Normal file
652
admin/exhibithall_sa.php
Normal file
@ -0,0 +1,652 @@
|
|||||||
|
<?
|
||||||
|
/*
|
||||||
|
This file is part of the 'Science Fair In A Box' project
|
||||||
|
SFIAB Website: http://www.sfiab.ca
|
||||||
|
|
||||||
|
Copyright (C) 2010 David Grant <dave@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_once('../common.inc.php');
|
||||||
|
require_once('judges.inc.php'); /* for getJudgingEligibilityCode() */
|
||||||
|
require_once('anneal.inc.php');
|
||||||
|
|
||||||
|
if($_SERVER['SERVER_ADDR']) {
|
||||||
|
echo "This script must be run from the command line";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$action = '';
|
||||||
|
if($argv[1] == '--images') {
|
||||||
|
$action = 'images';
|
||||||
|
}
|
||||||
|
|
||||||
|
//function TRACE() { }
|
||||||
|
//function TRACE_R() { }
|
||||||
|
function TRACE($str) { print($str); }
|
||||||
|
function TRACE_R($array) { print_r($array); }
|
||||||
|
|
||||||
|
|
||||||
|
function point_rotate($x, $y, $deg)
|
||||||
|
{
|
||||||
|
/* Use - orienttaiotn because rotation is always done from dest->src */
|
||||||
|
$r = deg2rad(-$deg);
|
||||||
|
return array(round($x*cos($r) - $y*sin($r), 6), round($x*sin($r) + $y*cos($r), 6));
|
||||||
|
}
|
||||||
|
|
||||||
|
function point_translate($x, $y, $dx, $dy)
|
||||||
|
{
|
||||||
|
return array ($x+$dx, $y+$dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
function is_point_in_object($x, $y, $o)
|
||||||
|
{
|
||||||
|
/* Translate the point to the object origin */
|
||||||
|
list($x, $y) = point_translate($x, $y, -$o['x'], -$o['y']);
|
||||||
|
/* Rotate the point to the object's frame of reference*/
|
||||||
|
list($x, $y) = point_rotate($x, $y, -$o['orientation']);
|
||||||
|
/* Is it within the object now ? */
|
||||||
|
if(abs($x) <= $o['w2'] && abs($y) <= $o['h2'])
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function queue_new()
|
||||||
|
{
|
||||||
|
return array('head' => NULL, 'tail' => NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function grid_path_cmp($a, $b)
|
||||||
|
{
|
||||||
|
/* This must return an integer! Strange-things(tm) happen if it doesn't */
|
||||||
|
if($a['distance'] == $b['distance']) return 0;
|
||||||
|
return ($a['distance'] < $b['distance']) ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function grid_path_check(&$i_eh, &$queue, &$loc, &$end, $ix, $iy)
|
||||||
|
{
|
||||||
|
$next_loc =& $i_eh[$ix][$iy];
|
||||||
|
// TRACE("Checking next loc($ix,$iy) ({$next_loc['x']},{$next_loc['y']})\n");
|
||||||
|
|
||||||
|
/* Don't revisit anything */
|
||||||
|
if($next_loc['visited'] == true) {
|
||||||
|
// TRACE(" Already Visited.\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$next_loc['visited'] = true;
|
||||||
|
|
||||||
|
if(count($next_loc['ids']) != 0) {
|
||||||
|
// TRACE(" Object occupying this gridloc.\n");
|
||||||
|
/* There's something here, can't do anything */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$next_loc['distance'] = distance($next_loc['x'], $next_loc['y'], $end['x'], $end['y']);
|
||||||
|
$next_loc['path_length'] = $loc['path_length'] + 1;
|
||||||
|
// TRACE(" distance={$next_loc['distance']}, path_length={$next_loc['path_length']}\n");
|
||||||
|
|
||||||
|
/* Add to processing queue in order */
|
||||||
|
array_push($queue, $next_loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
function grid_path($src, $dst)
|
||||||
|
{
|
||||||
|
global $exhibithall;
|
||||||
|
$i_eh = &$exhibithall[$src['exhibithall_id']];
|
||||||
|
$start = &$i_eh[$src['front_grid_ix']][$src['front_grid_iy']];
|
||||||
|
$end = &$i_eh[$dst['front_grid_ix']][$dst['front_grid_iy']];
|
||||||
|
|
||||||
|
// TRACE("Path ({$start['x']},{$start['y']}) -> ({$end['x']},{$end['y']})\n");
|
||||||
|
|
||||||
|
/* Clean out temp data */
|
||||||
|
for($x=0;$x<$i_eh['grid_w']; $x++) {
|
||||||
|
for($y=0;$y<$i_eh['grid_h']; $y++) {
|
||||||
|
$i_eh[$x][$y]['visited'] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// print_r($i_eh);
|
||||||
|
|
||||||
|
/* Seed the exploration queue */
|
||||||
|
$queue = array();
|
||||||
|
$start['distance'] = distance($start['x'], $start['y'], $end['x'], $end['y']);
|
||||||
|
$start['path_length'] = 0;
|
||||||
|
$start['visited'] = true;
|
||||||
|
array_push($queue, $start);
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
if(count($queue) == 0) break;
|
||||||
|
|
||||||
|
// print_r($queue);
|
||||||
|
/* Dequeue head */
|
||||||
|
$loc = array_shift($queue);
|
||||||
|
|
||||||
|
/* Cut it off after a long walk */
|
||||||
|
if($loc['path_length'] > 25) break;
|
||||||
|
|
||||||
|
// TRACE("Dequeue: ({$loc['x']},{$loc['y']})\n");
|
||||||
|
|
||||||
|
/* Is this our destionation ? */
|
||||||
|
if($loc['x'] == $end['x'] && $loc['y'] == $end['y']) {
|
||||||
|
// TRACE("Found destination, path_length={$loc['path_length']}\n");
|
||||||
|
return $loc['path_length'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* All 4 directions */
|
||||||
|
if($loc['ix'] > 0) grid_path_check($i_eh, $queue,$loc, $end, $loc['ix']-1, $loc['iy']);
|
||||||
|
if($loc['ix'] < $i_eh['grid_w']-1) grid_path_check($i_eh, $queue, $loc, $end, $loc['ix']+1, $loc['iy']);
|
||||||
|
if($loc['iy'] > 0) grid_path_check($i_eh, $queue, $loc, $end, $loc['ix'], $loc['iy']-1);
|
||||||
|
if($loc['iy'] < $i_eh['grid_h']-1) grid_path_check($i_eh, $queue, $loc, $end, $loc['ix'], $loc['iy']+1);
|
||||||
|
usort($queue, 'grid_path_cmp');
|
||||||
|
}
|
||||||
|
// TRACE("No path found\n");
|
||||||
|
// exit;
|
||||||
|
return 100;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TRACE("<pre>\n");
|
||||||
|
|
||||||
|
/* Load exhibit halls */
|
||||||
|
$exhibithall = array();
|
||||||
|
$q = mysql_query("SELECT * FROM exhibithall WHERE type='exhibithall'");
|
||||||
|
TRACE("Loading exhibit halls...\n");
|
||||||
|
while(($r = mysql_fetch_assoc($q))) {
|
||||||
|
$r['divs'] = unserialize($r['divs']);
|
||||||
|
$r['cats'] = unserialize($r['cats']);
|
||||||
|
$exhibithall[$r['id']] = $r;
|
||||||
|
TRACE(" - {$r['name']}\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Load objects */
|
||||||
|
$objects = array();
|
||||||
|
$q = mysql_query("SELECT * FROM exhibithall WHERE type='wall' OR type='project'");
|
||||||
|
TRACE("Loading objects...\n");
|
||||||
|
while(($r = mysql_fetch_assoc($q))) {
|
||||||
|
$r['divs'] = unserialize($r['divs']);
|
||||||
|
$r['cats'] = unserialize($r['cats']);
|
||||||
|
$objects[$r['id']] = $r;
|
||||||
|
}
|
||||||
|
TRACE(count($objects)." objects loaded.\n");
|
||||||
|
|
||||||
|
|
||||||
|
/* Compute stuff */
|
||||||
|
foreach($objects as $oid=>$o) {
|
||||||
|
$objects[$oid]['w2'] = $o['w']/2;
|
||||||
|
$objects[$oid]['h2'] = $o['h']/2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The grid size is the smallest object dimension */
|
||||||
|
$grid_size = 100;
|
||||||
|
foreach($objects as $oid=>$o) {
|
||||||
|
if($grid_size > $o['w']) $grid_size = $o['w'];
|
||||||
|
if($grid_size > $o['h']) $grid_size = $o['h'];
|
||||||
|
}
|
||||||
|
$grid_size /= 2;
|
||||||
|
|
||||||
|
TRACE("Grid size: {$grid_size}m\n");
|
||||||
|
|
||||||
|
/* Assign objects to grid locations */
|
||||||
|
foreach($exhibithall as &$i_eh) {
|
||||||
|
TRACE("Assigning objects to grid locations for {$i_eh['name']}...\n");
|
||||||
|
$ix = 0;
|
||||||
|
$i_eh['grid_w'] = 0;
|
||||||
|
$i_eh['grid_h'] = 0;
|
||||||
|
for($x=0;$x<=$i_eh['w'];$x+=$grid_size,$ix++) {
|
||||||
|
if($ix <= $i_eh['grid_w']) $i_eh['grid_w'] = $ix+1;
|
||||||
|
$iy = 0;
|
||||||
|
for($y=0;$y<=$i_eh['h'];$y+=$grid_size,$iy++) {
|
||||||
|
if($iy <= $i_eh['grid_h']) $i_eh['grid_h'] = $iy+1;
|
||||||
|
/* Initialize element if required */
|
||||||
|
if(!is_array($i_eh[$ix][$iy])) {
|
||||||
|
$i_eh[$ix][$iy] = array( 'x' => $x, 'ix' => $ix,
|
||||||
|
'y' => $y, 'iy' => $iy,
|
||||||
|
'ids' => array(),
|
||||||
|
'project_front' => 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scan all objects */
|
||||||
|
foreach($objects as $oid=>$o) {
|
||||||
|
if($o['exhibithall_id'] != $i_eh['id']) continue;
|
||||||
|
if(is_point_in_object($x, $y, $o)) {
|
||||||
|
$i_eh[$ix][$iy]['ids'][] = $oid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TRACE("Grid locations: {$i_eh['grid_w']}x{$i_eh['grid_h']}\n");
|
||||||
|
}
|
||||||
|
TRACE("Done.\n");
|
||||||
|
|
||||||
|
//print_r($exhibithall);
|
||||||
|
|
||||||
|
function distance($x1,$y1,$x2,$y2)
|
||||||
|
{
|
||||||
|
return sqrt( ($x1-$x2)*($x1-$x2)+($y1-$y2)*($y1-$y2) );
|
||||||
|
}
|
||||||
|
|
||||||
|
TRACE("Computing gridlocation of front of projects...\n");
|
||||||
|
foreach($objects as $oid=>$o) {
|
||||||
|
if($o['type'] != 'project') continue;
|
||||||
|
|
||||||
|
/* Get a pointer to the exhibit hall */
|
||||||
|
$i_eh = &$exhibithall[$o['exhibithall_id']];
|
||||||
|
|
||||||
|
/* Compute the xy of the front (that's the bottom edge of the unrotate project) */
|
||||||
|
$fx = 0;
|
||||||
|
$fy = $o['h2'];
|
||||||
|
// TRACE("Front orig: ($fx,$fy)\n");
|
||||||
|
|
||||||
|
/* Rotate the point */
|
||||||
|
list($fx, $fy) = point_rotate($fx, $fy, $o['orientation']);
|
||||||
|
// TRACE("Front rotate by {$o['orientation']}: ($fx,$fy) \n");
|
||||||
|
/* Translate it to the proper position of the object */
|
||||||
|
list($fx, $fy) = point_translate($fx, $fy, $o['x'], $o['y']);
|
||||||
|
|
||||||
|
// TRACE("Front: ($fx,$fy) $grid_size\n");
|
||||||
|
/* Snap to grid offsets */
|
||||||
|
$gx = intval($fx / $grid_size); //* $grid_size;
|
||||||
|
$gy = intval($fy / $grid_size); //* $grid_size;
|
||||||
|
|
||||||
|
// TRACE("Search grid around $gx, $gy\n");
|
||||||
|
/* Search around that grid for a free spot, closest to $fx,$fy,
|
||||||
|
* with no objects and no object_front */
|
||||||
|
$smallest_d = $i_eh['w'];
|
||||||
|
$smallest_ix = 0;
|
||||||
|
$smallest_iy = 0;
|
||||||
|
$found = false;
|
||||||
|
for($x = $gx - 1; $x <= $gx + 1; $x++) {
|
||||||
|
for($y = $gy - 1; $y <= $gy + 1; $y++) {
|
||||||
|
// TRACE("At ($x, $y) :\n");
|
||||||
|
// print_r($i_eh[$x][$y]);
|
||||||
|
if(count($i_eh[$x][$y]['ids'])) continue;
|
||||||
|
if($i_eh[$x][$y]['project_front'] > 0) continue;
|
||||||
|
|
||||||
|
/* Check distance */
|
||||||
|
$d = distance($i_eh[$x][$y]['x'], $i_eh[$x][$y]['y'], $fx, $fy);
|
||||||
|
if($d < $smallest_d) {
|
||||||
|
$smallest_d = $d;
|
||||||
|
$smallest_ix = $x;
|
||||||
|
$smallest_iy = $y;
|
||||||
|
$found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($found == false) {
|
||||||
|
echo "ERROR: couldn't find project front for:\n";
|
||||||
|
print_r($o);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$i_eh[$smallest_ix][$smallest_iy]['project_front'] = $oid;
|
||||||
|
$objects[$oid]['front_x'] = $fx;
|
||||||
|
$objects[$oid]['front_y'] = $fy;
|
||||||
|
$objects[$oid]['front_grid_ix'] = $smallest_ix;
|
||||||
|
$objects[$oid]['front_grid_iy'] = $smallest_iy;
|
||||||
|
}
|
||||||
|
TRACE("Done.\n");
|
||||||
|
|
||||||
|
//print_r($objects);
|
||||||
|
|
||||||
|
$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 Projects...\n");
|
||||||
|
$projects = array();
|
||||||
|
$q = mysql_query("SELECT projects.* FROM projects, registrations
|
||||||
|
WHERE
|
||||||
|
projects.year='{$config['FAIRYEAR']}'
|
||||||
|
AND registrations.id = projects.registrations_id
|
||||||
|
".getJudgingEligibilityCode());
|
||||||
|
while($p = mysql_fetch_object($q)) {
|
||||||
|
$qq = mysql_query("SELECT grade,schools_id FROM students WHERE registrations_id='{$p->registrations_id}'");
|
||||||
|
$num_students = mysql_num_rows($qq);
|
||||||
|
$grade = 0;
|
||||||
|
$schools_id = 0;
|
||||||
|
while($s = mysql_fetch_assoc($qq)) {
|
||||||
|
if($s['grade'] > $grade) {
|
||||||
|
$grade = $s['grade'];
|
||||||
|
$schools_id = $s['schools_id'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$projects[$p->id] = array(
|
||||||
|
'projects_id' => $p->id,
|
||||||
|
'div' => $p->projectdivisions_id,
|
||||||
|
'cat' => $p->projectcategories_id,
|
||||||
|
'grade' => $grade,
|
||||||
|
'schools_id' => $schools_id,
|
||||||
|
'req_electricity' => $p->req_electricity,
|
||||||
|
'projectnumber' => $p->projectnumber,
|
||||||
|
'floornumber' => $p->floornumber,
|
||||||
|
'num_students' => $num_students);
|
||||||
|
}
|
||||||
|
TRACE(count($projects)." projects loaded.\n");
|
||||||
|
|
||||||
|
|
||||||
|
switch($action) {
|
||||||
|
case 'images':
|
||||||
|
exhibithall_images();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Compute closest projects to each project */
|
||||||
|
$project_distance = 100 / $grid_size;
|
||||||
|
foreach($objects as $oid=>$o) {
|
||||||
|
if($o['type'] != 'project') continue;
|
||||||
|
|
||||||
|
TRACE("Computing paths for {$o['name']}...\n");
|
||||||
|
|
||||||
|
/* Get a pointer to the exhibit hall */
|
||||||
|
$i_eh = &$exhibithall[$o['exhibithall_id']];
|
||||||
|
|
||||||
|
/* Starting grid location */
|
||||||
|
$grid_start = &$i_eh[$o['front_grid_ix']][$o['front_grid_iy']];
|
||||||
|
|
||||||
|
/* Path to all other objects in the same exhibit hall */
|
||||||
|
foreach($objects as $d_oid=>$d_o) {
|
||||||
|
if($d_oid == $oid) continue;
|
||||||
|
if($o['exhibithall_id'] != $d_o['exhibithall_id']) continue;
|
||||||
|
|
||||||
|
$d = grid_path($o, $d_o);
|
||||||
|
$objects[$oid]['nearby_projects'][] = array('distance' => $d, 'id'=>$d_oid);
|
||||||
|
if($d < $project_distance) $project_distance = $d;
|
||||||
|
}
|
||||||
|
/* Use the grid_path_cmp to sort the projects based on 'distance' */
|
||||||
|
usort($objects[$oid]['nearby_projects'], 'grid_path_cmp');
|
||||||
|
}
|
||||||
|
TRACE("Project Distance: {$project_distance} hops\n");
|
||||||
|
|
||||||
|
/* Compute project distances */
|
||||||
|
foreach($objects as $oid=>$o) {
|
||||||
|
if($o['type'] != 'project') continue;
|
||||||
|
foreach($objects[$oid]['nearby_projects'] as &$nearby_project) {
|
||||||
|
$nearby_project['project_distance'] = $nearby_project['distance'] / $project_distance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Build a list of floor objects for the annealer*/
|
||||||
|
$floor_objects = array();
|
||||||
|
$x = 0;
|
||||||
|
foreach($objects as $oid=>$o) {
|
||||||
|
if($o['type'] != 'project') continue;
|
||||||
|
$objects[$oid]['floor_object_offset'] = $x; /* The same as the annealer bucket id */
|
||||||
|
$floor_objects[$x++] = &$objects[$oid];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Project floor location cost:
|
||||||
|
* - Keep divisions together
|
||||||
|
* - keep grades together
|
||||||
|
* - a project should have one of the same school nearby or adjacent, but not a lot nearby
|
||||||
|
*/
|
||||||
|
function project_cost(&$annealer, $bucket_id, $ids)
|
||||||
|
{
|
||||||
|
global $floor_objects, $projects, $exhibithall, $objects;
|
||||||
|
$cost = 0;
|
||||||
|
|
||||||
|
/* Get the floor object */
|
||||||
|
$o = &$floor_objects[$bucket_id];
|
||||||
|
|
||||||
|
/* The exhibit hall */
|
||||||
|
$eh = &$exhibithall[$o['exhibithall_id']];
|
||||||
|
|
||||||
|
if(count($ids) == 0) {
|
||||||
|
// TRACE("No items in bucket, returning 0.\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(count($ids) > 1) {
|
||||||
|
echo "More than one item in bucket! Bug somewhere.\n";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get the project info */
|
||||||
|
$p = &$projects[$ids[0]];
|
||||||
|
|
||||||
|
$school_match = 0;
|
||||||
|
$div_match = 0;
|
||||||
|
$grade_match = 0;
|
||||||
|
|
||||||
|
$x = 0;
|
||||||
|
// TRACE("Cost for bucket $bucket_id...\n");
|
||||||
|
$min = $p['grade'];
|
||||||
|
$max = $p['grade'];
|
||||||
|
foreach($o['nearby_projects'] as &$n) {
|
||||||
|
|
||||||
|
/* Get the nearby project object*/
|
||||||
|
$nearby_o = &$objects[$n['id']];
|
||||||
|
|
||||||
|
// TRACE(" Scanning nearby location {$n['id']} (distance={$n['distance']})\n");
|
||||||
|
// print_r($nearby_o);
|
||||||
|
|
||||||
|
$nearby_bucket_id = $nearby_o['floor_object_offset'];
|
||||||
|
$nearby_bkt = &$annealer->bucket[$nearby_bucket_id];
|
||||||
|
|
||||||
|
// TRACE(" Bucket id: {$nearby_bucket_id} with ".count($nearby_bkt)." items\n");
|
||||||
|
if(count($nearby_bkt) == 0) continue;
|
||||||
|
|
||||||
|
$nearby_p = &$projects[$nearby_bkt[0]];
|
||||||
|
|
||||||
|
/* Only consider closest 5 projects for school */
|
||||||
|
if($nearby_p['schools_id'] == $p['schools_id']) {
|
||||||
|
if($x < 5) $school_match++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Closest 5 projects for divs */
|
||||||
|
if($nearby_p['div'] == $p['div']) {
|
||||||
|
if($x < 5) $div_match++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Closest 10 for grade variance */
|
||||||
|
if($x < 10) {
|
||||||
|
if($nearby_p['grade'] < $min) $min = $nearby_p['grade'];
|
||||||
|
if($nearby_p['grade'] > $max) $max = $nearby_p['grade'];
|
||||||
|
}
|
||||||
|
$x++;
|
||||||
|
if($x == 10) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($school_match == 0) {
|
||||||
|
// TRACE(" No school nearby\n");
|
||||||
|
$cost += 5;
|
||||||
|
}
|
||||||
|
if($school_match > 2) {
|
||||||
|
// TRACE(" Too many schools bunched up\n");
|
||||||
|
$cost += 2 * ($school_match-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($div_match < 2) {
|
||||||
|
// TRACE(" No divs nearby\n");
|
||||||
|
$cost += 20;
|
||||||
|
}
|
||||||
|
if($div_match > 4) {
|
||||||
|
// TRACE(" Divs bunching up\n");
|
||||||
|
$cost += 10 * ($div_match - 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($max - $min > 0) {
|
||||||
|
/* Don't want bunching up grades eitehr */
|
||||||
|
// TRACE(" Grades too spread out\n");
|
||||||
|
$cost += 50 * ($max-$min);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make sure this project is allowed in this exhibit hall */
|
||||||
|
if(!in_array($p['div'], $eh['divs']))
|
||||||
|
$cost += 1000;
|
||||||
|
if(!in_array($p['cat'], $eh['cats']))
|
||||||
|
$cost += 1000;
|
||||||
|
|
||||||
|
/* Make sure this project is allowed in this floor object too */
|
||||||
|
if(count($o['divs']) > 0 && !in_array($p['div'], $o['divs']))
|
||||||
|
$cost += 1000;
|
||||||
|
if(count($o['cats']) > 0 && !in_array($p['cat'], $o['cats']))
|
||||||
|
$cost += 1000;
|
||||||
|
|
||||||
|
/* Match electricity */
|
||||||
|
if($p['req_electricity'] == 'yes' && $o['has_electricity'] == 'no') {
|
||||||
|
$cost += 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TRACE("Cost for bucket $bucket_id = $cost\n");
|
||||||
|
return $cost;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function project_bucket_ids($annealer, $bucket_id)
|
||||||
|
{
|
||||||
|
global $floor_objects, $objects;
|
||||||
|
$recompute_ids = array($bucket_id);
|
||||||
|
|
||||||
|
/* Get the floor object */
|
||||||
|
$o = &$floor_objects[$bucket_id];
|
||||||
|
|
||||||
|
/* Find the 10 closest projects */
|
||||||
|
$x = 0;
|
||||||
|
foreach($o['nearby_projects'] as &$n) {
|
||||||
|
$nearby_o = &$objects[$n['id']];
|
||||||
|
$recompute_ids[] = $nearby_o['floor_object_offset'];
|
||||||
|
$x++;
|
||||||
|
if($x == 15) break;
|
||||||
|
}
|
||||||
|
return $recompute_ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$e = 10 * ($config['effort'] / 1000) * pow(count($projects), 1.3333);
|
||||||
|
$project_ids = array_keys($projects);
|
||||||
|
|
||||||
|
//array_splice($project_ids, 20);
|
||||||
|
|
||||||
|
$a = new annealer(count($floor_objects), 125, $e, 0.9,
|
||||||
|
project_cost, $project_ids);
|
||||||
|
$a->set_max_items_per_bucket(1);
|
||||||
|
//$a->set_delta_cost_bucket_ids_callback(project_bucket_ids);
|
||||||
|
$a->anneal();
|
||||||
|
|
||||||
|
for($x=0;$x<$a->num_buckets; $x++) {
|
||||||
|
$bkt = $a->bucket[$x];
|
||||||
|
if(count($bkt) > 1) {
|
||||||
|
TRACE("Assigned more than one project to bucket $x\n");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
if(count($bkt) == 0) continue;
|
||||||
|
|
||||||
|
/* Get the project id in this bucket */
|
||||||
|
$projects_id = array_shift($bkt);
|
||||||
|
echo "p:$projects_id, n:{$floor_objects[$x]['floornumber']}\n";
|
||||||
|
/* Get the floor object for the same bucket and floor number */
|
||||||
|
$projects[$projects_id]['floornumber'] = $floor_objects[$x]['floornumber'];
|
||||||
|
}
|
||||||
|
|
||||||
|
print_r($projects);
|
||||||
|
|
||||||
|
/* Assign floor numbers */
|
||||||
|
mysql_query("UPDATE projects SET floornumber=0 WHERE year='{$config['FAIRYEAR']}'");
|
||||||
|
|
||||||
|
foreach($projects as $pid=>$p) {
|
||||||
|
mysql_query("UPDATE projects SET floornumber='{$p['floornumber']}' WHERE id='$pid'");
|
||||||
|
TRACE("Project $pid => Floor number {$p['floornumber']}\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
TRACE("</pre>");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function exhibithall_images()
|
||||||
|
{
|
||||||
|
global $exhibithall, $objects, $projects;
|
||||||
|
|
||||||
|
/* Assign project IDs to objects */
|
||||||
|
foreach($objects as $oid=>$o) {
|
||||||
|
foreach($projects as $pid=>$p) {
|
||||||
|
if($p['floornumber'] == $o['floornumber']) {
|
||||||
|
$objects[$oid]['projects_id'] = $pid;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach($exhibithall as &$i_eh) {
|
||||||
|
|
||||||
|
$i = imagecreatetruecolor($i_eh['w']*100, $i_eh['h']*100);
|
||||||
|
$c_grey = imagecolorallocate($i, 128, 128, 128);
|
||||||
|
$c_white = imagecolorallocate($i, 255, 255, 255);
|
||||||
|
$c_black = imagecolorallocate($i, 0, 0, 0);
|
||||||
|
|
||||||
|
// Fill the background with the color selected above.
|
||||||
|
imagefill($i, 0, 0, $c_white);
|
||||||
|
imagerectangle($i, 0, 0, $i_eh['w']*100 - 1, $i_eh['h']*100 - 1, $c_black);
|
||||||
|
|
||||||
|
for($ix=0;$ix<=$i_eh['grid_w'];$ix++) {
|
||||||
|
for($iy=0;$iy<=$i_eh['grid_h'];$iy++) {
|
||||||
|
$l = $i_eh[$ix][$iy];
|
||||||
|
if(count($l['ids']) > 0) {
|
||||||
|
imageellipse($i, $l['x']*100, $l['y']*100, 1, 1, $c_black);
|
||||||
|
} else {
|
||||||
|
imageellipse($i, $l['x']*100, $l['y']*100, 1, 1, $c_grey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach($objects as $oid=>$o) {
|
||||||
|
if($o['exhibithall_id'] != $i_eh['id']) continue;
|
||||||
|
|
||||||
|
list($x1,$y1) = point_rotate(-$o['w2'], -$o['h2'], $o['orientation']);
|
||||||
|
list($x2,$y2) = point_rotate($o['w2'], $o['h2'], $o['orientation']);
|
||||||
|
imagerectangle($i, ($o['x']+$x1)*100, ($o['y']+$y1)*100, ($o['x']+$x2)*100, ($o['y']+$y2)*100, $c_black);
|
||||||
|
|
||||||
|
$p = $projects[$o['projects_id']];
|
||||||
|
imagestring($i, 4, $o['x']*100 - 30, $o['y']*100 - 35, "{$o['floornumber']} ({$p['projects_id']})", $c_black);
|
||||||
|
imagestring($i, 4, $o['x']*100 - 30, $o['y']*100 - 20, "gr:{$p['grade']} ", $c_black);
|
||||||
|
$d = $divshort[$p['div']];
|
||||||
|
imagestring($i, 4, $o['x']*100 - 30, $o['y']*100 - 5, "d:$d ({$p['div']})", $c_black);
|
||||||
|
imagestring($i, 4, $o['x']*100 - 30, $o['y']*100 + 10, "s:{$p['schools_id']}", $c_black);
|
||||||
|
imagestring($i, 4, $o['x']*100 - 30, $o['y']*100 + 25, "e:{$p['req_electricity']}", $c_black);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
imagepng($i, "./eh-{$i_eh['id']}.png");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
Loading…
Reference in New Issue
Block a user