num_buckets = $num_buckets; $this->start_temp = $start_temp; $this->start_moves = $start_moves; $this->cost_function_callback = $cost_function_cb; unset($this->pick_move_callback); unset($this->update_callback); $this->bucket_cost = array(); $this->bucket = array(); $this->iterations = 0; $this->items_per_bucket = count($items) / $num_buckets; $this->rate = $rate; $ipb = ceil($this->items_per_bucket); $i=0; $this->cost = 0; for($x=0; $x<$num_buckets; $x++) { unset($b); $b = array(); for($y=0;$y<$ipb; $y++) { if($i == count($items)) break; $b[] = $items[$i]; $i++; } $this->bucket[] = $b; $c = $this->cost_function($x); $this->bucket_cost[] = $c; $this->cost += $c; } TRACE("Annealer setup: T={$this->start_temp}, ". "M={$this->start_moves}, Bkts={$this->num_buckets}, ". "Cost={$this->cost}\n"); } function set_pick_move($func) { $this->pick_move_callback = $func; } function set_update_callback($func) { $this->update_callback = $func; } function pick_move() { /* Pick a bucket and item */ while(1) { $b1 = rand(0, $this->num_buckets - 1); if(count($this->bucket[$b1]) > 0) break; } $i1 = rand(0, count($this->bucket[$b1]) -1); /* Pick a csecond bucket that is different thatn the first */ $b2 = rand(0, $this->num_buckets - 2); if($b2 >= $b1) $b2++; /* Picket an item, or a blank, in the second bucket */ $i2 = rand(0, count($this->bucket[$b2])); if($i2 == count($this->bucket[$b2])) $i2 = -1; // TRACE("Move ($b1,$i1)<->($b2,$i2)\n"); return array($b1, $i1, $b2, $i2); } function cost_function($b) { $bkt = $this->bucket[$b]; $cb = $this->cost_function_callback; $c = $cb($this, $b, $bkt); // $this->print_bucket($b); // print("Computed cost to be: $c\n"); return $c; } function compute_delta_cost($move) { list($b1, $i1, $b2, $i2) = $move; if($b1 == $b2) { return $this->compute_delta_cost_same_bucket($move); } $cost = 0; $b1_old = $this->bucket[$b1]; $b2_old = $this->bucket[$b2]; $b1_new = array(); $b2_new = array(); /* Make 2 new bucket lists */ for($x=0; $xbucket[$b1] = $b1_new; $this->bucket[$b2] = $b2_new; /* Compute costs */ $cost -= $this->bucket_cost[$b1]; $cost -= $this->bucket_cost[$b2]; $c1 = $this->cost_function($b1); $c2 = $this->cost_function($b2); $cost += $c1 + $c2; /* 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)); } 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; $xbucket[$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) { list($b1, $i1, $b2, $i2) = $move; list($c1, $b1_new, $c2, $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; } } function anneal() { $temperature = $this->start_temp; $current_cost = $this->cost; $last_cost = 0; $last_cost_count = 0; if($this->num_buckets <= 1) { TRACE("Only one Bucket, nothing to anneal.\n"); return; } // $this->print_buckets(); $estimated_iterations = ceil(log(0.1 / $this->start_temp, $this->rate)); $iterations = 0; while(1) { $moves = $this->start_moves; for($m = 0; $m<$moves; $m++) { // $this->print_buckets(); /* Pick 2 moves at random */ 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 */ list($delta_c, $movedata) = $this->compute_delta_cost($move); $r = floatval(rand()) / floatval(getrandmax()); /* Decide if we want to keep it */ $e = exp(-$delta_c / $temperature); // TRACE("r=$r, exp=$e, delta=$delta_c\n"); if($r < exp(-$delta_c / $temperature)) { /* Yes, we do, record the move */ $this->accept_move($move, $movedata); $current_cost += $delta_c; $n_accepted++; // if($current_cost < $this->cost) $this->cost = $current_cost; // TRACE("Move accepted, cost=$current_cost\n"); } else { // TRACE("Move rejected\n"); } $this->iterations++; if($this->iterations % 10000 == 0) { TRACE(" {$this->iterations} iterations, cost={$this->cost}, temperature=$temperature\n"); // $this->print_buckets(); } if($this->cost == 0) { /* If we manage to get to a 0 cost * solution, don't look any more */ break; } } $iterations++; if(isset ($this->update_callback)) { $cb = $this->update_callback; $cb($iterations, $estimated_iterations); } if($this->cost == 0) break; if($this->cost == $last_cost) { $last_cost_count ++; } else { $last_cost = $this->cost; $last_cost_count=0; } if($temperature < 0.1 && $last_cost_count > 10) break; // TRACE("Cost is {$this->cost}\n"); $temperature *= $this->rate; } TRACE("Annealing complete. {$this->iterations} iterations. Final cost is {$this->cost}\n"); } function print_bucket($x) { $b = $this->bucket[$x]; print("Bucket $x: (cost: {$this->bucket_cost[$x]})\n"); print(" "); for($y=0;$ynum_buckets; $x++) { $this->print_bucket($x); } } } ?>