<? /* * 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 = ''; switch ($argv[1]) { case '--images': $action = 'images'; break; case '--pn': $action = 'pn'; break; } // 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 = $pdo->prepare("SELECT * FROM exhibithall WHERE type='exhibithall'"); $q->execute(); TRACE("Loading exhibit halls...\n"); while (($r = $q->fetch(PDO::FETCH_ASSOC))) { $r['divs'] = unserialize($r['divs']); $r['cats'] = unserialize($r['cats']); $exhibithall[$r['id']] = $r; TRACE(" - {$r['name']}\n"); } /* Load objects */ $objects = array(); $q = $pdo->prepare("SELECT * FROM exhibithall WHERE type='wall' OR type='project'"); $q->execute(); TRACE("Loading objects...\n"); while (($r = $q->fetch([PDO::FETCH_ASSOC]))) { $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"); // print_r($exhibithall); // print_r($objects); $div = array(); TRACE("Loading Project Divisions...\n"); $q = $pdo->prepare("SELECT * FROM projectdivisions WHERE year='{$config['FAIRYEAR']}' ORDER BY id"); while ($r = $q->fetch(PDO::FETCH_OBJ)) { $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 = $pdo->prepare("SELECT * FROM projectcategories WHERE year=? ORDER BY id"); $q->execute([$config['FAIRYEAR']]); while ($r = $q->fetch(PDO::FETCH_OBJ)) { $catshort[$r->id] = $r->category_shortform; $cat[$r->id] = $r->category; TRACE(" {$r->id} - {$r->category}\n"); } TRACE("Loading Projects...\n"); $projects = array(); $q = $pdo->prepare("SELECT projects.* FROM projects, registrations WHERE projects.year=? AND registrations.id = projects.registrations_id " . getJudgingEligibilityCode()); $q->execute([$config['FAIRYEAR']]); while ($p = $q->fetch(PDO::FETCH_OBJ)) { $qq = $pdo->prepare("SELECT grade,schools_id FROM students WHERE registrations_id=?"); $qq->execute([$p->registrations_id]); $num_students = $qq->rowCouunt(); $grade = 0; $schools_id = 0; while ($s = $qq->fetch(PDO::FETCH_ASSOC)) { 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"); if ($action == 'pn') { TRACE("Generating Project Numbers from Floor Locations...\n"); foreach ($projects as $p) { $c = $catshort[$p['cat']]; $d = $divshort[$p['div']]; $n = sprintf('%03d', $p['floornumber']); $pn = "$c $n $d"; TRACE("Project {$p['projects_id']} at loc {$p['floornumber']}: $pn\n"); $q = $pdo->prepare("UPDATE projects SET projectnumber=? WHERE id=?"); $q->execute([$pn,$p['projects_id']]); } TRACE("Done.\n"); exit; } /* 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"); 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"); 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 */ $q = $pdo->prepare("UPDATE projects SET floornumber=0 WHERE year=?"); $q->execute([$config['FAIRYEAR']]); foreach ($projects as $pid => $p) { $q = $pdo->prepare("UPDATE projects SET floornumber=? WHERE id=?"); $q->execute([$p['floornumber'],$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"); } } ?>