/*
 * SimAnnealing.java
 * Created on Mar 28, 2005
 */
package search;

import java.util.Random;

/**
 * Simulated annealing of a spin glass.
 */
public final class SimAnnealing {
    
    private static final Random rand = new Random();
    private static final int NUM_SPINS = 100;
    private static final int MAX_ITERATIONS = 10000;
    private static final double[] COOLING_RATES = {0.1, 0.01, 0.001};

    private SimAnnealing() {}
    
    private static void simAnnealing(SpinGlass spinGlass, double coolingRate) {
        double currentEnergy = spinGlass.energy();
        
        for (int t = 1; t <= MAX_ITERATIONS ; t++) {
            int randSpin = rand.nextInt(spinGlass.size());
            spinGlass.flip(randSpin);
            double newEnergy = spinGlass.energy();
            double deltaEnergy = newEnergy - currentEnergy;
            
            // energy decreases, accept
            if (deltaEnergy < 0) {
                // if energy doesn't decrease too much, stop
                if (deltaEnergy > -.0001) return;
                currentEnergy = newEnergy;
            }
            // otherwise, accept with probability p
            else {
                double p = Math.exp(-coolingRate * t * deltaEnergy);
                if (rand.nextDouble() < p) {
                    currentEnergy = newEnergy;
                } else {
                    spinGlass.flip(randSpin);
                }
            }
            
        }
    }
    
    public static void main(String[] args) {
        
        boolean[] spins = new boolean[NUM_SPINS];
        double[] energies = new double[NUM_SPINS];
        double minEnergy = 0;
        
        for (int i = 0; i < NUM_SPINS; i++) {
            spins[i] = rand.nextBoolean();
            energies[i] = rand.nextGaussian();
            minEnergy -= Math.abs(energies[i]);
        }
        
        System.out.println("min energy: " + minEnergy);
        
        for(int i = 0; i < COOLING_RATES.length; i++) {
            System.out.println();
            System.out.println("cooling rate:" + COOLING_RATES[i]);
            SpinGlass spinGlass = new SpinGlass(spins, energies);
            System.out.println("start energy: " + spinGlass.energy());
            simAnnealing(spinGlass, COOLING_RATES[i]);
            System.out.println("final energy: " + spinGlass.energy());
        }
    }

}
