From de761c8b3ee25bbb851c9e2a28f6e3b18b9dea25 Mon Sep 17 00:00:00 2001 From: dave Date: Mon, 22 Feb 2010 18:45:58 +0000 Subject: [PATCH] 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). --- admin/anneal.inc.php | 91 ++++-- admin/exhibithall_sa.php | 652 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 719 insertions(+), 24 deletions(-) create mode 100644 admin/exhibithall_sa.php diff --git a/admin/anneal.inc.php b/admin/anneal.inc.php index c7a16ba..4250d59 100644 --- a/admin/anneal.inc.php +++ b/admin/anneal.inc.php @@ -1,19 +1,23 @@ cost_function_callback = $cost_function_cb; unset($this->pick_move_callback); unset($this->update_callback); + unset($this->delta_cost_bucket_ids_callback); $this->bucket_cost = array(); + $this->bucket_cost_old = array(); $this->bucket = array(); $this->iterations = 0; - $this->items_per_bucket = count($items) / $num_buckets; + $this->items_per_bucket = ceil(count($items) / $num_buckets); $this->rate = $rate; $this->max_items_per_bucket = 0; $ipb = ceil($this->items_per_bucket); $i=0; $this->cost = 0; + /* Assign to buckets */ for($x=0; $x<$num_buckets; $x++) { unset($b); $b = array(); @@ -43,6 +50,10 @@ class annealer { $i++; } $this->bucket[] = $b; + } + + /* Then do costs after all bucket assignments are done */ + for($x=0; $x<$num_buckets; $x++) { $c = $this->cost_function($x); $this->bucket_cost[] = $c; $this->cost += $c; @@ -56,17 +67,21 @@ class annealer { { $this->pick_move_callback = $func; } - function set_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) { $this->max_items_per_bucket = $num; } + function pick_move() { /* Pick a bucket and item */ @@ -109,38 +124,66 @@ class annealer { list($b1, $i1, $b2, $i2) = $move; 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; + /* Save the old lists for easy restore */ $b1_old = $this->bucket[$b1]; $b2_old = $this->bucket[$b2]; + + /* Setup new costs */ + $this->bucket_cost_new = $this->bucket_cost; - if($i2 != -1) { + /* Compute new lists with swapped elements */ + if($i2 != -1) { /* Swap */ array_splice($this->bucket[$b1], $i1, 1, $b2_old[$i2]); array_splice($this->bucket[$b2], $i2, 1, $b1_old[$i1]); - } else { + } else { /* Move one to other */ array_splice($this->bucket[$b1], $i1, 1); $this->bucket[$b2][] = $b1_old[$i1]; } - /* Compute costs */ - $cost -= $this->bucket_cost[$b1]; - $cost -= $this->bucket_cost[$b2]; + /* Get the lists of buckets we need to recompute, by default + * just b1 and 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); - $c2 = $this->cost_function($b2); +// TRACE("Recompute IDs:\n"); +// 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 */ $this->bucket[$b1] = $b1_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) { list($b1, $i1, $b2, $i2) = $move; @@ -150,10 +193,10 @@ class annealer { $b_old = $this->bucket[$b1]; $b_new = array(); - /* Make a new bucket list */ + /* Make a new bucket list for($x=0; $xbucket[$b1] = $b_new; - /* Compute costs */ + /* Compute costs $cost -= $this->bucket_cost[$b1]; $c1 = $this->cost_function($b1); $cost += $c1; - /* Return to the original bucket lists */ + /* 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) { 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_cost[$b1] = $c1; - if($b1 != $b2) { - $this->bucket[$b2] = $b2_new; - $this->bucket_cost[$b2] = $c2; - } + if($b1 != $b2) $this->bucket[$b2] = $b2_new; + + $this->bucket_cost = $this->bucket_cost_new; } function anneal() @@ -204,6 +246,7 @@ class annealer { // $this->print_buckets(); $estimated_iterations = ceil(log(0.1 / $this->start_temp, $this->rate)); +// print_r($this); $iterations = 0; while(1) { $moves = $this->start_moves; diff --git a/admin/exhibithall_sa.php b/admin/exhibithall_sa.php new file mode 100644 index 0000000..c18b4ff --- /dev/null +++ b/admin/exhibithall_sa.php @@ -0,0 +1,652 @@ + + + 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. +*/ +?> +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("
\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("
"); + + + +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"); + + } +} + + + +?>