/*
* This file is part of MazeSolver.
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright (c) 2014 MazeSolver
* Sergio M. Afonso Fumero <theSkatrak@gmail.com>
* Kevin I. Robayna Hernández <kevinirobaynahdez@gmail.com>
*/
/**
* @file SimulatedAnnealingAgent.java
* @date 14/12/2014
*/
package es.ull.mazesolver.agent;
import java.awt.Color;
import java.awt.Point;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.Collections;
import es.ull.mazesolver.gui.configuration.AgentConfigurationPanel;
import es.ull.mazesolver.gui.configuration.SimulatedAnnealingAgentConfigurationPanel;
import es.ull.mazesolver.gui.environment.Environment;
import es.ull.mazesolver.maze.MazeCell;
import es.ull.mazesolver.util.Direction;
/**
* Agente que implementa el algoritmo meta-heurístico "Recocido simulado".
*/
public class SimulatedAnnealingAgent extends HeuristicAgent {
private static final long serialVersionUID = 1607730980597645713L;
private static final double DISTANCE_BOOSTING = 5.0;
private static final int DEFAULT_TEMPERATURE = 5000;
private static final double DEFAULT_COOLING_RATE = 0.005;
private transient int m_actual_temp;
private int m_initial_temp;
private double m_cooling_rate;
/**
* Crea el agente en el entorno indicado con una configuración por defecto que
* no está adaptada a ningún laberinto en concreto.
*
* @param env
* Entorno en el que colocar al agente.
*/
public SimulatedAnnealingAgent (Environment env) {
super(env);
m_actual_temp = m_initial_temp = DEFAULT_TEMPERATURE;
m_cooling_rate = DEFAULT_COOLING_RATE;
}
/**
* @return La temperatura inicial usada por el agente.
*/
public int getInitialTemperature () {
return m_initial_temp;
}
/**
* Cambia la temperatura inicial del agente.
*
* @param temp La nueva temperatura inicial.
*/
public void setInitialTemperature (int temp) {
m_initial_temp = temp;
}
/**
* @return El ratio de enfriamiento por iteración del agente.
*/
public double getCoolingRate () {
return m_cooling_rate;
}
/**
* Cambia el ratio de enfriamiento del agente por iteración.
*
* @param rate El nuevo ratio de enfriamiento.
*/
public void setCoolingRate (double rate) {
m_cooling_rate = rate;
}
/*
* (non-Javadoc)
*
* @see es.ull.mazesolver.agent.Agent#getAlgorithmName()
*/
@Override
public String getAlgorithmName () {
return "Simulated Annealing";
}
/*
* (non-Javadoc)
*
* @see es.ull.mazesolver.agent.Agent#getAlgorithmColor()
*/
@Override
public Color getAlgorithmColor () {
return Color.CYAN;
}
/*
* (non-Javadoc)
*
* @see es.ull.mazesolver.agent.Agent#getNextMovement()
*/
@Override
public Direction getNextMovement () {
// Enfriamos la temperatura si aún está caliente
if (m_actual_temp > 1)
m_actual_temp *= 1.0 - m_cooling_rate;
// Obtenemos los estados vecinos y los permutamos aleatoriamente
ArrayList <Direction> neighbours = getNeighbours();
Collections.shuffle(neighbours);
// Si no hay vecinos no nos movemos a ningún lado
if (neighbours.isEmpty())
return Direction.NONE;
// Seleccionamos aleatoriamente alguna dirección de movimiento de acuerdo
// a la temperatura actual
Point exit = m_env.getMaze().getExit();
for (int i = 0; i < neighbours.size(); i++) {
Direction dir = neighbours.get(i);
double diff = m_dist.distance(dir.movePoint(m_pos), exit) - m_dist.distance(m_pos, exit);
// Si la nueva posición está más cerca de la salida se acepta
// directamente, si no se acepta con una probabilidad directamente
// proporcional a la temperatura actual. Aumentamos la distancia real
// entre las 2 posiciones para que el proceso de convergencia acabe menos
// drásticamente.
if (diff < 0.0 || Math.exp((-DISTANCE_BOOSTING * diff) / m_actual_temp) > Math.random())
return dir;
}
return Direction.NONE;
}
/*
* (non-Javadoc)
*
* @see es.ull.mazesolver.agent.Agent#resetMemory()
*/
@Override
public void resetMemory () {
m_actual_temp = m_initial_temp;
}
/*
* (non-Javadoc)
*
* @see es.ull.mazesolver.agent.Agent#getConfigurationPanel()
*/
@Override
public AgentConfigurationPanel getConfigurationPanel () {
return new SimulatedAnnealingAgentConfigurationPanel(this);
}
/*
* (non-Javadoc)
*
* @see es.ull.mazesolver.agent.Agent#clone()
*/
@Override
public Object clone () {
SimulatedAnnealingAgent ag = new SimulatedAnnealingAgent(m_env);
ag.setAgentColor(getAgentColor());
ag.setDistanceCalculator(m_dist);
ag.m_cooling_rate = m_cooling_rate;
ag.m_initial_temp = m_initial_temp;
return ag;
}
/**
* Extrae la información del objeto a partir de una forma serializada del
* mismo.
*
* @param input
* Flujo de entrada con la información del objeto.
* @throws ClassNotFoundException
* Si se trata de un objeto de otra clase.
* @throws IOException
* Si no se puede leer el flujo de entrada.
*/
private void readObject (ObjectInputStream input) throws ClassNotFoundException, IOException {
input.defaultReadObject();
m_actual_temp = m_initial_temp;
}
/**
* Busca las celdas adyacentes accesibles desde la posición del agente. No
* tiene en cuenta otros agentes.
*
* @return Una lista con las direcciones vecinas (accesibles) del agente.
*/
public ArrayList <Direction> getNeighbours () {
ArrayList <Direction> neighbours = new ArrayList <Direction>();
for (int i = 1; i < Direction.MAX_DIRECTIONS; i++) {
Direction dir = Direction.fromIndex(i);
if (m_env.look(m_pos, dir) != MazeCell.Vision.WALL)
neighbours.add(dir);
}
return neighbours;
}
}