/******************************************************************************* * TurtleKit 3 - Agent Based and Artificial Life Simulation Platform * Copyright (C) 2011-2014 Fabien Michel * * 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/>. ******************************************************************************/ package turtlekit.kernel; import static turtlekit.kernel.TurtleKit.Option.patch; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicInteger; import madkit.kernel.Activator; import madkit.kernel.Madkit; import madkit.kernel.MadkitClassLoader; import madkit.kernel.Probe; import madkit.kernel.Watcher; import madkit.simulation.probe.PropertyProbe; import turtlekit.agr.TKOrganization; import turtlekit.cuda.CudaEngine; import turtlekit.cuda.CudaGPUGradientsPhero; import turtlekit.cuda.CudaPheromone; import turtlekit.pheromone.DefaultCPUPheromoneGrid; import turtlekit.pheromone.Pheromone; public class TKEnvironment extends Watcher { private String community; private int width; private int height; private Map<String, Pheromone<Float>> pheromones; private Map<String,Probe<Turtle>> turtleProbes; private Patch[] patchGrid; boolean wrapMode; private boolean cudaOn; private int heightRadius; private int widthRadius; private final static transient AtomicInteger turtleCounter = new AtomicInteger(0); private int[] neighborsIndexes;//TODO or remove protected boolean GPU_GRADIENTS = false; private boolean synchronizeGPU = true; public boolean isSynchronizeGPU() { return synchronizeGPU; } public void setSynchronizeGPU(boolean synchronizeGPU) { this.synchronizeGPU = synchronizeGPU; } public TKEnvironment(){ pheromones = new TreeMap<String, Pheromone<Float>>(); } public Collection<Pheromone<Float>> getPheromones(){ return pheromones.values(); } @Override protected void activate() { GPU_GRADIENTS = Boolean.parseBoolean(getMadkitProperty("GPU_gradients")); // setLogLevel(Level.ALL); community = getMadkitProperty(TurtleKit.Option.community); setWidth(Integer.parseInt(getMadkitProperty(TurtleKit.Option.envWidth))); setHeight(Integer.parseInt(getMadkitProperty(TurtleKit.Option.envHeight))); wrapMode = ! isMadkitPropertyTrue(TurtleKit.Option.noWrap); cudaOn = isMadkitPropertyTrue(TurtleKit.Option.cuda); if(logger != null){ logger.info("----------------------CUDA ON "+isCudaOn()); logger.info("----------------------GPU_GRADIENTS "+GPU_GRADIENTS); } if(cudaOn){ // Turtle.generator = new GPU_PRNG(12134); //TODO } initPatchGrid(); // request my role so that the viewer can probe me requestRole(community, TKOrganization.MODEL_GROUP, TKOrganization.ENVIRONMENT_ROLE); // keeping the turtles group alive requestRole(community, TKOrganization.TURTLES_GROUP, TKOrganization.ENVIRONMENT_ROLE); // this probe is used to initialize the agents' environment field addProbe(new AgentsProbe()); } @Override protected void end() { super.end(); for (Pheromone<Float> i : pheromones.values()) { if(i instanceof CudaPheromone){ ((CudaPheromone) i).freeMemory(); } } pheromones = null; patchGrid = null; turtleProbes = null; } private void initPatchGrid() { patchGrid = new Patch[width * height]; neighborsIndexes = new int[width * height * 8]; Class<? extends Patch> patchClass; try { patchClass = (Class<? extends Patch>) MadkitClassLoader.getLoader().loadClass(getMadkitProperty(patch)); } catch (ClassNotFoundException e) { patchClass = Patch.class; getLogger().severe(e.toString()+" -> using "+patchClass.getName()); } for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { final int retrieveIndex = retrieveIndex(i, j); try { final Patch patch = patchClass.newInstance(); patchGrid[retrieveIndex] = patch; patch.setCoordinates(i, j); patch.setEnvironment(this); } catch (InstantiationException | IllegalAccessException e) { e.printStackTrace(); } int k = retrieveIndex*8; neighborsIndexes[k]=get1DIndex(i, normalizeCoordinate(j-1,height));//TODO not used and thus useless (check that) neighborsIndexes[++k]=get1DIndex(i, normalizeCoordinate(j+1,height)); neighborsIndexes[++k]=get1DIndex(normalizeCoordinate(i-1,width), normalizeCoordinate(j-1,height)); neighborsIndexes[++k]=get1DIndex(normalizeCoordinate(i-1,width), j); neighborsIndexes[++k]=get1DIndex(normalizeCoordinate(i-1,width), normalizeCoordinate(j+1,height)); neighborsIndexes[++k]=get1DIndex(normalizeCoordinate(i+1,width), normalizeCoordinate(j-1,height)); neighborsIndexes[++k]=get1DIndex(normalizeCoordinate(i+1,width), j); neighborsIndexes[++k]=get1DIndex(normalizeCoordinate(i+1,width), normalizeCoordinate(j+1,height)); } } } private int retrieveIndex(int u, int v) { return v * width + u; } protected Patch getPatch(int i, int j) { return patchGrid[retrieveIndex(normalizeCoordinate(i, width), normalizeCoordinate(j, height))]; } final int normalizeCoordinate(int a, final int dimensionThickness) { if (wrapMode) { a %= dimensionThickness; return a < 0 ? a + dimensionThickness : a; } if (a >= dimensionThickness) return dimensionThickness - 1; else return a < 0 ? 0 : a; } final double normalizeCoordinate(double a, final int dimensionThickness) { if (wrapMode) { a %= dimensionThickness; return a < 0 ? a + dimensionThickness : a; } if (a >= dimensionThickness) return dimensionThickness - .01; else return a < 0 ? 0 : a; } /** * Returns the normalized value of x, so that * it is inside the environment's boundaries * * @param x x-coordinate * @return the normalized value */ final public double normalizeX(double x){ return normalizeCoordinate(x, width); } /** * Returns the normalized value of y, so that * it is inside the environment's boundaries * * @param y y-coordinate * @return the normalized value */ final public double normalizeY(double y){ return normalizeCoordinate(y, height); } /** * Returns the normalized value of x, so that * it is inside the environment's boundaries * * @param x x-coordinate * @return the normalized value */ final public double normalizeX(int x){ return normalizeCoordinate(x, width); } /** * Returns the normalized value of y, so that * it is inside the environment's boundaries * * @param y y-coordinate * @return the normalized value */ final public double normalizeY(int y){ return normalizeCoordinate(y, height); } /** * @param x absolute * @param y absolute * @return the absolute index in a 1D data grid representing a 2D grid of * width,height size. This should be used with {@link Turtle#xcor()} and * {@link Turtle#ycor()}. * Its purpose is to be used on a * Pheromone */ public int get1DIndex(int xcor, int ycor){ return normalizeCoordinate(xcor, width) + normalizeCoordinate(ycor, height) * width; } protected void update(){ // executePheromonesSequentialy(); executePheromonesInParallel(); //myDynamic } /** * */ private void executePheromonesSequentialy() { for (Pheromone<?> pheromone : pheromones.values()) { // pheromone.set((int) (Math.random()*200), (int) (Math.random()*200), 1E38f); //TODO nice art pheromone.diffusionAndEvaporation(); } if (cudaOn) { CudaEngine.cuCtxSynchronizeAll(); } } /** * Launch a turtle with predefined coordinates * * @param t * @param x * @param y * @return the ID given to the turtle */ public int createTurtle(Turtle t, double x, double y){ t.x = x; t.y = y; launchAgent(t); return t.getID(); } /** * Launch a turtle with a random location * * @param t * @return the ID given to the turtle */ public int createTurtle(Turtle t){ return createTurtle(t, Double.MAX_VALUE, Double.MAX_VALUE); } private void executePheromonesInParallel() { final Collection<Pheromone<Float>> pheromonesList = getPheromones(); if (! pheromonesList.isEmpty()) { final ArrayList<Callable<Void>> workers = new ArrayList<>( pheromonesList.size()); for (final Pheromone<Float> pheromone : pheromonesList) { workers.add(new Callable<Void>() { @Override public Void call() throws Exception { pheromone.diffusionAndEvaporation(); return null; } }); } try { Activator.getMadkitServiceExecutor().invokeAll(workers); if (isCudaOn() && synchronizeGPU) { CudaEngine.cuCtxSynchronizeAll(); } } catch (InterruptedException e) { e.printStackTrace(); } } } /** * reset max values for rendering purposes */ protected void resetPheroMaxValues(){ for (Pheromone<Float> phero : pheromones.values()) { phero.setMaximum(0f); } } float smell(String pheromone, int x, int y) { return getPheromone(pheromone).get(x, y); } void emit(String pheromone, int x, int y, float value) { final Pheromone<Float> p = getPheromone(pheromone); p.set(x, y, p.get(x,y) + value); } public int getWidth() { return width; } public int getHeight() { return height; } /** * Gets the corresponding pheromone or create a new one using defaults * parameters : 50% for both the evaporation rate and * the diffusion rate. * @param name the pheromone's name * @return the pheromone */ public Pheromone<Float> getPheromone(String name) { return getPheromone(name,50,50); } /** * Gets the corresponding pheromone or create a new one using the * parameters if available: The first float is the evaporation rate and the second * is the diffusion rate. * * @param name the pheromone's name * @param parameters the first float is the evaporation rate and the second * is the diffusion rate. * @return the pheromone */ public Pheromone<Float> getPheromone(String name, int evaporationPercentage, int diffusionPercentage) { return getPheromone(name, evaporationPercentage / 100f, diffusionPercentage / 100f); } /** * Gets the corresponding pheromone or create a new one using the * parameters if available: The first float is the evaporation rate and the second * is the diffusion rate. * * @param name the pheromone's name * @param parameters the first float is the evaporation rate and the second * is the diffusion rate. * @return the pheromone */ public Pheromone<Float> getPheromone(String name, float evaporationPercentage, float diffusionPercentage) { Pheromone<Float> phero = pheromones.get(name); if (phero == null) { synchronized (pheromones) { phero = pheromones.get(name); if (phero == null) { if (cudaOn && CudaEngine.isCudaAvailable() ) { // phero = new CudaPheromone(name, width, height, evaporationPercentage, diffusionPercentage); phero = createCudaPheromone(name, evaporationPercentage, diffusionPercentage); } else{ //TODO experimental // phero = new CPU_SobelPheromone(name, getWidth(), getHeight(), evaporationPercentage, diffusionPercentage, neighborsIndexes); // phero = new JavaPheromone(name, getWidth(), getHeight(), evaporationPercentage, diffusionPercentage, neighborsIndexes); phero = new DefaultCPUPheromoneGrid(name, width, height, evaporationPercentage, diffusionPercentage, neighborsIndexes);// phero = new FloatPheromoneGrid(name, getWidth(), getHeight(), evaporationPercentage, diffusionPercentage, neighborsIndexes); } pheromones.put(name, phero); } } } return phero; } protected Pheromone<Float> createCudaPheromone(String name, float evaporationPercentage, float diffusionPercentage){ if(GPU_GRADIENTS) return new CudaGPUGradientsPhero(name, getWidth(), getHeight(), evaporationPercentage, diffusionPercentage); return new CudaPheromone(name, getWidth(), getHeight(), evaporationPercentage, diffusionPercentage); } /** * @param community * the group's community. * @param group * the targeted group. * @param role * the desired role. * @return a list of Turtles currently having this role */ public List<Turtle> getTurtlesWithRoles(final String community, final String group, final String role){ if(turtleProbes == null){ turtleProbes = new HashMap<>(); } final String key = community+";"+group+";"+role; Probe<Turtle> probe = turtleProbes.get(key); if(probe == null){ turtleProbes.put(key, probe = new Probe<Turtle>(community,group,role)); addProbe(probe); } return probe.getCurrentAgentsList(); } /** * Gets the turtles with this role in the default community and group * * @param role * @return a list of turtles */ public List<Turtle> getTurtlesWithRoles(final String role){ return getTurtlesWithRoles(community, TKOrganization.TURTLES_GROUP, role); } class AgentsProbe extends PropertyProbe<Turtle, TKEnvironment>{ public AgentsProbe() { super(community, TKOrganization.TURTLES_GROUP, TKOrganization.TURTLE_ROLE, "environment"); } // @Override // protected void adding(List<Turtle> agents) { // Turtle agentTest = agents.get(0); // Patch p; // if(agentTest.x == Double.MAX_VALUE || agentTest.y == Double.MAX_VALUE){ // p = getPatch(width /2, height / 2); // for (Turtle turtle : agents) { // turtle.x = p.i; // turtle.y = p.j; // } // } // else{ // p = getPatch((int) agentTest.x, (int) agentTest.y); // } // p.installTurtles(agents); // for (Turtle turtle : agents) { // turtle.setID(turtleCounter.incrementAndGet()); // setPropertyValue(turtle, Environment.this); // } // } // protected void adding(Turtle agent) { if(logger != null) logger.finer("adding : "+agent); agent.setID(turtleCounter.incrementAndGet()); setPropertyValue(agent, TKEnvironment.this); Patch p; if(agent.x == Double.MAX_VALUE || agent.y == Double.MAX_VALUE){ // int xcor = Turtle.generator.nextInt(getWidth()); // int ycor = Turtle.generator.nextInt(getHeight()); p = getPatch(Turtle.generator.nextInt(getWidth()), Turtle.generator.nextInt(getHeight())); agent.x = p.x; agent.y = p.y; } else{ agent.x = normalizeX(agent.x); agent.y = normalizeY(agent.y); p = getPatch((int) agent.x, (int) agent.y); } p.addAgent(agent); // patchGrid[0].addAgent(agent); } protected void removing(Turtle agent) { agent.getPatch().removeAgent(agent); // agent.setPatch(null); } } /** * @return the patchGrid */ protected final Patch[] getPatchGrid() { return patchGrid; } /** * This offers a convenient way to create a main method * that launches a simulation using the environment * class under development. * This call only works in the main method of the environment. * * @param args * MaDKit or TurtleKit options * @see #executeThisAgent(int, boolean, String...) * @since TurtleKit 3.0.0.1 */ protected static void executeThisEnvironment(String... args) { StackTraceElement element = null; for (StackTraceElement stackTraceElement : new Throwable().getStackTrace()) { if(stackTraceElement.getMethodName().equals("main")){ element = stackTraceElement; break; } } final ArrayList<String> arguments = new ArrayList<>(Arrays.asList( Madkit.BooleanOption.desktop.toString(),"false", Madkit.Option.configFile.toString(), "turtlekit/kernel/turtlekit.properties", TurtleKit.Option.environment.toString(), element.getClassName())); if (args != null) { arguments.addAll(Arrays.asList(args)); } new Madkit(arguments.toArray(new String[0])); } /** * @return the cudaOn */ public boolean isCudaOn() { return cudaOn; } /** * @param width the width to set */ final void setWidth(int width) { this.width = width; widthRadius = width / 2; } /** * @param height the height to set */ final void setHeight(int height) { this.height = height; heightRadius = height / 2; } /** * @return the envHeightRadius */ final int getHeightRadius() { return heightRadius; } /** * @return the envWidthRadius */ final int getWidthRadius() { return widthRadius; } /** * Keep the agents synchronized with the environment dynamics * * @param synchronizedEnvironment */ public void synchronizeEnvironment(boolean synchronizedEnvironment) { this.synchronizeGPU= synchronizedEnvironment; } }