/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may
* obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied.
*
* See the License for the specific language governing permissions
* and limitations under the License.
*/
package siebog.agents.xjaf.aco.tsp;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;
import javax.ejb.Remote;
import javax.ejb.Stateful;
import org.jboss.vfs.VirtualFile;
import siebog.agents.Agent;
import siebog.agents.AgentInitArgs;
import siebog.agents.XjafAgent;
import siebog.interaction.ACLMessage;
import siebog.interaction.Performative;
import siebog.utils.LoggerUtil;
/**
* Implementation of a map, in form of an agent.
*
* @author <a href="mailto:tntvteod@neobee.net">Teodor Najdan Trifunov</a>
* @author <a href="mailto:milan.laketic@yahoo.com">Milan Laketic</a>
*/
@Stateful
@Remote(Agent.class)
public class Map extends XjafAgent {
private static final long serialVersionUID = 4998652517108886246L;
// TSP graph (given by a set of (x,y)-points).
private List<Node> nodes;
// Initial pheromone value.
private float tau0;
// Pheromone values for individual graph edges.
private float[][] pheromone;
// Weight of the best tour found so far.
private float bestTourWeight = Float.MAX_VALUE;
// Best tour found so far.
private List<Integer> bestTour = new ArrayList<>();
// Represents a heuristic for program termination. When this variable reaches
// MAX_STATIONARY_ITERATIONS, the program terminates.
private int nIterationsBestTourNotUpdated = 0;
// Heuristic boundary value for program termination.
private static final int MAX_STATIONARY_ITERATIONS = 500;
private boolean done = false;
@Override
protected void onInit(AgentInitArgs args) {
LoggerUtil.log("Map opened.", true);
loadMap(args.get("fileName", null).toString());
}
@Override
protected void onMessage(ACLMessage message) {
final String content = message.content;
if (message.performative == Performative.REQUEST) {
ACLMessage reply = message.makeReply(Performative.INFORM);
if (content.equals("MapSize?")) {
if (!done) {
reply.content = getMapSize() + "";
} else {
reply.content = "DONE";
}
} else if (content.startsWith("PheromoneLevels?")) {
String[] parts = content.split(" ");
StringBuilder pheromoneLevels = new StringBuilder();
int i = Integer.parseInt(parts[1]);
pheromoneLevels.append("PheromoneLevels:");
for (int j = 2; j < parts.length; ++j) {
int newJ = Integer.parseInt(parts[j]);
pheromoneLevels.append(" ").append(getPheromoneLevel(i, newJ)).append(" ")
.append(getEdgeWeight(i, newJ));
}
reply.content = pheromoneLevels.toString();
} else if (content.startsWith("EdgeWeight?")) {
String[] parts = content.split(" ");
reply.content = String.valueOf(getEdgeWeight(Integer.parseInt(parts[1]),
Integer.parseInt(parts[2])));
}
msm().post(reply);
} else if (message.performative == Performative.INFORM) {
if (content.startsWith("UpdateBestTour")) {
String[] parts = content.split(" ");
float newTourWeight = Float.parseFloat(parts[1]);
LoggerUtil.log("No. of iterations for the best tour not being updated: " + nIterationsBestTourNotUpdated, true);
if (nIterationsBestTourNotUpdated >= MAX_STATIONARY_ITERATIONS) {
LoggerUtil.log("Done.", true);
done = true;
return;
}
nIterationsBestTourNotUpdated++;
if (bestTourWeight > newTourWeight) {
nIterationsBestTourNotUpdated = 0;
bestTourWeight = newTourWeight;
bestTour.clear();
for (int i = 2; i < parts.length; ++i)
bestTour.add(Integer.parseInt(parts[i]) + 1);
LoggerUtil.log("Best tour so far has weight: " + bestTourWeight, true);
LoggerUtil.log("Best tour so far: " + bestTour, true);
}
} else if (content.startsWith("UpdatePheromone")) {
String[] parts = content.split(" ");
int i = Integer.parseInt(parts[1]);
int j = Integer.parseInt(parts[2]);
setPheromoneLevel(i, j, Float.parseFloat(parts[3]) * getPheromoneLevel(i, j)
+ Float.parseFloat(parts[4]));
} else if (content.startsWith("UpdateLocalPheromone")) {
String[] parts = content.split(" ");
int i = Integer.parseInt(parts[1]);
int j = Integer.parseInt(parts[2]);
float ksi = Float.parseFloat(parts[3]);
setPheromoneLevel(i, j, (1 - ksi) * getPheromoneLevel(i, j) + ksi * tau0);
}
} else if (message.performative == Performative.CANCEL) {
LoggerUtil.log("############# Canceled ###############.", true);
done = true;
}
}
/**
* Loads the world graph from the specified file (into 'nodes' list) and calculates initial
* pheromone level tau0 which is set for each edge in 'pheromone' matrix.
*/
private void loadMap(String mapName) {
File f = null;
nodes = new ArrayList<>();
URL url = ACOStarter.class.getResource("maps/" + mapName);
System.out.println(url);
if (url != null) {
if (url.toString().startsWith("vfs:/")) {
try {
URLConnection conn = new URL(url.toString()).openConnection();
VirtualFile vf = (VirtualFile)conn.getContent();
f = vf.getPhysicalFile();
} catch (Exception ex) {
ex.printStackTrace();
f = new File(".");
}
} else {
try {
f = new File(url.toURI());
} catch (URISyntaxException e) {
e.printStackTrace();
f = new File(".");
}
}
} else {
f = new File(mapName);
}
LoggerUtil.log("Loading map from: " + f.getAbsolutePath());
try (BufferedReader reader = new BufferedReader(new FileReader(f))) {
// skip preliminary info
for (int i = 0; i < 6; ++i)
reader.readLine();
// load the map
String line = null;
String[] parts = null;
while (!(line = reader.readLine()).equals("EOF")) {
parts = line.split(" ");
nodes.add(new Node(Float.parseFloat(parts[1]), Float.parseFloat(parts[2])));
}
// print the map
LoggerUtil.log("Map: ", true);
StringBuilder sb = new StringBuilder();
for (Node n : nodes)
sb.append(n).append(" ");
LoggerUtil.log(sb.toString(), true);
// initialize pheromone levels
int n = nodes.size();
float C = n * getAverageWeight();
tau0 = 1 / (n * C);
pheromone = new float[n][n];
for (int i = 0; i < n; ++i)
for (int j = 0; j < n; ++j)
pheromone[i][j] = tau0;
} catch (Exception ex) {
LoggerUtil.log(ex.getMessage(), true);
}
}
/**
* @return average edge weight (Euclid2D) of the currently loaded map.
*/
private float getAverageWeight() {
float result = 0f;
int n = nodes.size();
for (int i = 0; i < n; ++i)
for (int j = 0; j < n; ++j)
result += getEdgeWeight(i, j);
return result / (n * n);
}
public Integer getMapSize() {
return nodes.size();
}
/**
* @return current pheromone level of the edge between nodes i and j.
*/
public float getPheromoneLevel(int i, int j) {
return pheromone[i][j];
}
/**
* Set pheromone level of (i,j)-edge to 'val'.
*/
public void setPheromoneLevel(int i, int j, float val) {
pheromone[i][j] = val;
}
/**
* @return weight of the (i,j) edge (Euclid2D distance between node i and node j).
*/
public float getEdgeWeight(int i, int j) {
Node ni = nodes.get(i);
Node nj = nodes.get(j);
return (float) (Math.sqrt(Math.pow(ni.getX() - nj.getX(), 2)
+ Math.pow(ni.getY() - nj.getY(), 2)));
}
@Override
public void onTerminate() {
LoggerUtil.log("Map terminated.", true);
}
}