diff --git a/AMAKFX/README.md b/AMAKFX/README.md index 9bf1ac1c346140eee3787661c2c3da214377c7b5..4e966b1594a18ffbee697324a727d544130e40e3 100644 --- a/AMAKFX/README.md +++ b/AMAKFX/README.md @@ -1,13 +1,28 @@ -# This is the README from the old version of AMAK, some part may be outdated ! # +# This is a fork of AMAK, now using JavaFX instead of Swing # [The original repo](https://bitbucket.org/perlesa/amak/overview) -This repository contains a framework made to facilitate the development of multi-agent system. -Examples are available in the package fr.irit.smac.amak.examples. -# [Click here to download the latest standalone version](https://bitbucket.org/perlesa/amak/raw/master/Release/AmakFramework-standalone.jar?at=master) # +## AMAKFX changes (2019-07-25) : +These changes were made for a particular project, without much care for backward compatibility. But the overall philosophy should be the same. -# Recent changes # +### Possible breaking changes : ++ Anything related with the GUI in Swing. ++ Support for asynchronous agent has been dropped, you may still use then, but no promise are made for the visualization. + +### Changes : ++ Build system is now maven. ++ Use JavaFX instead of Swing. JavaFX run on a different thread than the simulation, when updating the GUI from the simulation thread you must be extra careful for : + + Running JavaFX code on the JavaFX thread, by only using AMAKFX methods, or by learning how to use JavaFX's RunLater (see AMAKFX's [RunLaterHelper](src/fr/irit/smac/amak/tools/RunLaterHelper.java)) + + Not overload JavaFX call queue with draw call, leading to unpredictable result. For that purpose a new option has been added to Configuration : waitForGui, default at true. Each simulation cycle, AMAK will wait for JavaFX call queue to be empty. But queue overload can still happen inside a cycle. Symptoms are : long freeze, part of GUI not updating, or becoming white. ++ The VUI has been overhauled, it now provide extra features : + + Drawable can detect events, and dispatch these events to linked drawables. + + VUI Explorer, a sided bar that show a list of visible Drawable in its VUI. Features : hovering an element in the VUI Explorer highlight it in the VUI. Search for element in the VUI Explorer with regex. Click an element to display additional info (when available). ++ Plotting is now done with JFreeChart, AMAKFX provide an helper class : AmakPlot. ++ Tabs can be drag-n-dropped to rearrange them. Dropping a tab outside the tab bar will open it in a new window (despite the mouse cursor showing it's impossible) ++ Changes on how logging work, the a Log object now accept multiple action. Added the Loggable interface,with default methods, allowing a class to easily log to a file. + +# Old AMAK README # ## 1.5.3 (11/28/2018) ## ### New features: none diff --git a/AMAKFX/src/fr/irit/smac/amak/Agent.java b/AMAKFX/src/fr/irit/smac/amak/Agent.java index 9685e065123e3ae277e7482e37e39789ce88c9f1..1de07ab93413f5fb8d9581c27fe6955f3862d71d 100644 --- a/AMAKFX/src/fr/irit/smac/amak/Agent.java +++ b/AMAKFX/src/fr/irit/smac/amak/Agent.java @@ -1,496 +1,517 @@ -package fr.irit.smac.amak; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import fr.irit.smac.amak.Amas.ExecutionPolicy; -import fr.irit.smac.amak.tools.Log; - -/** - * This class must be overridden by all agents - * - * @author Alexandre Perles - * - * @param <A> - * The kind of Amas the agent refers to - * @param <E> - * The kind of Environment the agent AND the Amas refer to - */ -public abstract class Agent<A extends Amas<E>, E extends Environment> implements Runnable { - /** - * Neighborhood of the agent (must refer to the same couple amas, environment - */ - protected final List<Agent<A, E>> neighborhood; - /** - * Criticalities of the neighbors (and it self) as perceived at the beginning of - * the agent's cycle - */ - protected final Map<Agent<A, E>, Double> criticalities = new HashMap<>(); - /** - * Last calculated criticality of the agent - */ - private double criticality; - /** - * Amas the agent belongs to - */ - protected final A amas; - /** - * Unique index to give unique id to each agent - */ - private static int uniqueIndex; - - /** - * The id of the agent - */ - private final int id; - /** - * The order of execution of the agent as computed by - * {@link Agent#_computeExecutionOrder()} - */ - private int executionOrder; - /** - * The parameters that can be user in the initialization process - * {@link Agent#onInitialization()} - */ - protected Object[] params; - - /** - * These phases are used to synchronize agents on phase - * - * @see fr.irit.smac.amak.Amas.ExecutionPolicy - * @author perles - * - */ - public enum Phase { - /** - * Agent is perceiving - */ - PERCEPTION, - /** - * Agent is deciding and acting - */ - DECISION_AND_ACTION, - /** - * Agent haven't started to perceive, decide or act - */ - INITIALIZING, - /** - * Agent is ready to decide - */ - PERCEPTION_DONE, - /** - * Agent is ready to perceive or die - */ - DECISION_AND_ACTION_DONE - } - - /** - * The current phase of the agent {@link Phase} - */ - protected Phase currentPhase = Phase.INITIALIZING; - - private boolean synchronous = true; - - /** - * The constructor automatically add the agent to the corresponding amas and - * initialize the agent - * - * @param amas - * Amas the agent belongs to - * @param params - * The params to initialize the agent - */ - public Agent(A amas, Object... params) { - this.id = uniqueIndex++; - this.params = params; - this.amas = amas; - neighborhood = new ArrayList<>(); - neighborhood.add(this); - onInitialization(); - if (!Configuration.commandLineMode) - onRenderingInitialization(); - - - if (amas != null) { - this.amas._addAgent(this); - } - } - - /** - * Add neighbors to the agent - * - * @param agents - * The list of agent that should be considered as neighbor - */ - @SafeVarargs - public final void addNeighbor(Agent<A, E>... agents) { - for (Agent<A, E> agent : agents) { - if (agent != null) { - neighborhood.add(agent); - criticalities.put(agent, Double.NEGATIVE_INFINITY); - } - } - } - - /** - * This method must be overridden by the agents. This method shouldn't make any - * calls to internal representation an agent has on its environment because - * these information maybe outdated. - * - * @return the criticality at a given moment - */ - protected double computeCriticality() { - return Double.NEGATIVE_INFINITY; - } - - protected void setAsynchronous() { - if (currentPhase != Phase.INITIALIZING) - Log.defaultLog.fatal("AMAK", "Asynchronous mode must be set during the initialization"); - this.synchronous = false; - } - /** - * This method must be overriden if you need to specify an execution order layer - * - * @return the execution order layer - */ - protected int computeExecutionOrderLayer() { - return 0; - } - - /** - * This method is called at the beginning of an agent's cycle - */ - protected void onAgentCycleBegin() { - - } - - /** - * This method is called at the end of an agent's cycle - */ - protected void onAgentCycleEnd() { - - } - - /** - * This method corresponds to the perception phase of the agents and must be - * overridden - */ - protected void onPerceive() { - - } - - /** - * This method corresponds to the decision phase of the agents and must be - * overridden - */ - protected void onDecide() { - - } - - /** - * This method corresponds to the action phase of the agents and must be - * overridden - */ - protected void onAct() { - - } - - /** - * In this method the agent should expose some variables with its neighbor - */ - protected void onExpose() { - - } - - /** - * This method should be used to update the representation of the agent for - * example in a VUI - */ - public void onUpdateRender() { - - } - - /** - * This method is now deprecated and should be replaced by onUpdateRender - * - * @deprecated Must be replaced by {@link #onUpdateRender()} - */ - @Deprecated - protected final void onDraw() { - - } - - /** - * Called when all initial agents have been created and are ready to be started - */ - protected void onReady() { - - } - - /** - * Called by the framework when all initial agents have been created and are - * almost ready to be started - */ - protected final void _onBeforeReady() { - criticality = computeCriticality(); - executionOrder = _computeExecutionOrder(); - } - - /** - * Called before all agents are created - */ - protected void onInitialization() { - - } - - /** - * Replaced by onInitialization - * - * @deprecated Must be replaced by {@link #onInitialization()} - */ - @Deprecated - protected final void onInitialize() { - - } - - /** - * Called to initialize the rendering of the agent - */ - protected void onRenderingInitialization() { - - } - - /** - * @deprecated This method is useless because the state of the agent is not - * supposed to evolve before or after its cycle. Use - * OnAgentCycleBegin/End instead. - * - * This method is final because it must not be implemented. - * Implement it will have no effect. - */ - @Deprecated - protected final void onSystemCycleBegin() { - - } - - /** - * @deprecated This method is useless because the state of the agent is not - * supposed to evolve before or after its cycle. Use - * OnAgentCycleBegin/End instead. - * - * This method is final because it must not be implemented. - * Implement it will have no effect. - */ - @Deprecated - protected final void onSystemCycleEnd() { - - } - - /** - * This method is called automatically and corresponds to a full cycle of an - * agent - */ - @Override - public void run() { - ExecutionPolicy executionPolicy = amas.getExecutionPolicy(); - if (executionPolicy == ExecutionPolicy.TWO_PHASES) { - - currentPhase = nextPhase(); - switch (currentPhase) { - case PERCEPTION: - phase1(); - amas.informThatAgentPerceptionIsFinished(); - break; - case DECISION_AND_ACTION: - phase2(); - amas.informThatAgentDecisionAndActionAreFinished(); - break; - default: - Log.defaultLog.fatal("AMAK", "An agent is being run in an invalid phase (%s)", currentPhase); - } - } else if (executionPolicy == ExecutionPolicy.ONE_PHASE) { - onePhaseCycle(); - amas.informThatAgentPerceptionIsFinished(); - amas.informThatAgentDecisionAndActionAreFinished(); - } - } - public void onePhaseCycle() { - currentPhase = Phase.PERCEPTION; - phase1(); - currentPhase = Phase.DECISION_AND_ACTION; - phase2(); - } - /** - * This method represents the perception phase of the agent - */ - protected final void phase1() { - onAgentCycleBegin(); - perceive(); - currentPhase = Phase.PERCEPTION_DONE; - } - - /** - * This method represents the decisionAndAction phase of the agent - */ - protected final void phase2() { - decideAndAct(); - executionOrder = _computeExecutionOrder(); - onExpose(); - if (!Configuration.commandLineMode) - onUpdateRender(); - onAgentCycleEnd(); - currentPhase = Phase.DECISION_AND_ACTION_DONE; - } - - /** - * Determine which phase comes after another - * - * @return the next phase - */ - private Phase nextPhase() { - switch (currentPhase) { - case PERCEPTION_DONE: - return Phase.DECISION_AND_ACTION; - case INITIALIZING: - case DECISION_AND_ACTION_DONE: - default: - return Phase.PERCEPTION; - } - } - - /** - * Compute the execution order from the layer and a random value. This method - * shouldn't be overridden. - * - * @return A number used by amak to determine which agent executes first - */ - protected int _computeExecutionOrder() { - return computeExecutionOrderLayer() * 10000 + amas.getEnvironment().getRandom().nextInt(10000); - } - - /** - * Perceive, decide and act - */ - void perceive() { - for (Agent<A, E> agent : neighborhood) { - criticalities.put(agent, agent.criticality); - } - onPerceive(); - // Criticality of agent should be updated after perception AND after action - criticality = computeCriticality(); - criticalities.put(this, criticality); - } - - /** - * A combination of decision and action as called by the framework - */ - private final void decideAndAct() { - onDecideAndAct(); - - criticality = computeCriticality(); - } - - /** - * Decide and act These two phases can often be grouped - */ - protected void onDecideAndAct() { - onDecide(); - onAct(); - } - - /** - * Convenient method giving the most critical neighbor at a given moment - * - * @param includingMe - * Should the agent also consider its own criticality - * @return the most critical agent - */ - protected final Agent<A, E> getMostCriticalNeighbor(boolean includingMe) { - List<Agent<A, E>> criticalest = new ArrayList<>(); - double maxCriticality = Double.NEGATIVE_INFINITY; - - if (includingMe) { - criticalest.add(this); - maxCriticality = criticalities.getOrDefault(criticalest, Double.NEGATIVE_INFINITY); - } - for (Entry<Agent<A, E>, Double> e : criticalities.entrySet()) { - if (e.getValue() > maxCriticality) { - criticalest.clear(); - maxCriticality = e.getValue(); - criticalest.add(e.getKey()); - } else if (e.getValue() == maxCriticality) { - criticalest.add(e.getKey()); - } - } - if (criticalest.isEmpty()) - return null; - return criticalest.get(getEnvironment().getRandom().nextInt(criticalest.size())); - } - - /** - * Get the latest computed execution order - * - * @return the execution order - */ - public int getExecutionOrder() { - return executionOrder; - } - - /** - * Getter for the AMAS - * - * @return the amas - */ - public A getAmas() { - return amas; - } - - /** - * Remove the agent from the system - */ - public void destroy() { - getAmas()._removeAgent(this); - } - - /** - * Agent toString - */ - @Override - public String toString() { - return String.format("Agent #%d", id); - } - - /** - * Getter for the current phase of the agent - * - * @return the current phase - */ - public Phase getCurrentPhase() { - return currentPhase; - } - - /** - * Return the id of the agent - * - * @return the id of the agent - */ - public int getId() { - return id; - } - - /** - * Getter for the environment - * - * @return the environment - */ - public E getEnvironment() { - return getAmas().getEnvironment(); - } - - public boolean isSynchronous() { - return synchronous ; - } -} +package fr.irit.smac.amak; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import fr.irit.smac.amak.Amas.ExecutionPolicy; +import fr.irit.smac.amak.tools.Log; +import fr.irit.smac.amak.ui.AmasMultiUIWindow; + +/** + * This class must be overridden by all agents + * + * @author Alexandre Perles + * + * @param <A> + * The kind of Amas the agent refers to + * @param <E> + * The kind of Environment the agent AND the Amas refer to + */ +public abstract class Agent<A extends Amas<E>, E extends Environment> implements Runnable { + + + /** + * Neighborhood of the agent (must refer to the same couple amas, environment + */ + protected final List<Agent<A, E>> neighborhood; + /** + * Criticalities of the neighbors (and it self) as perceived at the beginning of + * the agent's cycle + */ + protected final Map<Agent<A, E>, Double> criticalities = new HashMap<>(); + /** + * Last calculated criticality of the agent + */ + private double criticality; + /** + * Amas the agent belongs to + */ + protected final A amas; + /** + * Unique index to give unique id to each agent + */ + private static int uniqueIndex; + + /** + * The id of the agent + */ + private final int id; + /** + * The order of execution of the agent as computed by + * {@link Agent#_computeExecutionOrder()} + */ + private int executionOrder; + /** + * The parameters that can be user in the initialization process + * {@link Agent#onInitialization()} + */ + protected Object[] params; + + /** + * These phases are used to synchronize agents on phase + * + * @see fr.irit.smac.amak.Amas.ExecutionPolicy + * @author perles + * + */ + public enum Phase { + /** + * Agent is perceiving + */ + PERCEPTION, + /** + * Agent is deciding and acting + */ + DECISION_AND_ACTION, + /** + * Agent haven't started to perceive, decide or act + */ + INITIALIZING, + /** + * Agent is ready to decide + */ + PERCEPTION_DONE, + /** + * Agent is ready to perceive or die + */ + DECISION_AND_ACTION_DONE + } + + /** + * The current phase of the agent {@link Phase} + */ + protected Phase currentPhase = Phase.INITIALIZING; + + private boolean synchronous = true; + + /** + * The constructor automatically add the agent to the corresponding amas and + * initialize the agent + * + * @param amas + * Amas the agent belongs to + * @param params + * The params to initialize the agent + */ + public Agent(A amas, Object... params) { + this.id = uniqueIndex++; + this.params = params; + this.amas = amas; + neighborhood = new ArrayList<>(); + neighborhood.add(this); + onInitialization(); + if (!Configuration.commandLineMode) + onRenderingInitialization(); + + + if (amas != null) { + this.amas._addAgent(this); + } + } + + public Agent(AmasMultiUIWindow window, A amas, Object... params) { + + this.id = uniqueIndex++; + this.params = params; + this.amas = amas; + neighborhood = new ArrayList<>(); + neighborhood.add(this); + onInitialization(); + if (!Configuration.commandLineMode) + onRenderingInitialization(); + + + if (amas != null) { + this.amas._addAgent(this); + } + } + + /** + * Add neighbors to the agent + * + * @param agents + * The list of agent that should be considered as neighbor + */ + @SafeVarargs + public final void addNeighbor(Agent<A, E>... agents) { + for (Agent<A, E> agent : agents) { + if (agent != null) { + neighborhood.add(agent); + criticalities.put(agent, Double.NEGATIVE_INFINITY); + } + } + } + + /** + * This method must be overridden by the agents. This method shouldn't make any + * calls to internal representation an agent has on its environment because + * these information maybe outdated. + * + * @return the criticality at a given moment + */ + protected double computeCriticality() { + return Double.NEGATIVE_INFINITY; + } + + protected void setAsynchronous() { + if (currentPhase != Phase.INITIALIZING) + Log.defaultLog.fatal("AMAK", "Asynchronous mode must be set during the initialization"); + this.synchronous = false; + } + /** + * This method must be overriden if you need to specify an execution order layer + * + * @return the execution order layer + */ + protected int computeExecutionOrderLayer() { + return 0; + } + + /** + * This method is called at the beginning of an agent's cycle + */ + protected void onAgentCycleBegin() { + + } + + /** + * This method is called at the end of an agent's cycle + */ + protected void onAgentCycleEnd() { + + } + + /** + * This method corresponds to the perception phase of the agents and must be + * overridden + */ + protected void onPerceive() { + + } + + /** + * This method corresponds to the decision phase of the agents and must be + * overridden + */ + protected void onDecide() { + + } + + /** + * This method corresponds to the action phase of the agents and must be + * overridden + */ + protected void onAct() { + + } + + /** + * In this method the agent should expose some variables with its neighbor + */ + protected void onExpose() { + + } + + /** + * This method should be used to update the representation of the agent for + * example in a VUI + */ + public void onUpdateRender() { + + } + + /** + * This method is now deprecated and should be replaced by onUpdateRender + * + * @deprecated Must be replaced by {@link #onUpdateRender()} + */ + @Deprecated + protected final void onDraw() { + + } + + /** + * Called when all initial agents have been created and are ready to be started + */ + protected void onReady() { + + } + + /** + * Called by the framework when all initial agents have been created and are + * almost ready to be started + */ + protected final void _onBeforeReady() { + criticality = computeCriticality(); + executionOrder = _computeExecutionOrder(); + } + + /** + * Called before all agents are created + */ + protected void onInitialization() { + + } + + /** + * Replaced by onInitialization + * + * @deprecated Must be replaced by {@link #onInitialization()} + */ + @Deprecated + protected final void onInitialize() { + + } + + /** + * Called to initialize the rendering of the agent + */ + protected void onRenderingInitialization() { + + } + + /** + * @deprecated This method is useless because the state of the agent is not + * supposed to evolve before or after its cycle. Use + * OnAgentCycleBegin/End instead. + * + * This method is final because it must not be implemented. + * Implement it will have no effect. + */ + @Deprecated + protected final void onSystemCycleBegin() { + + } + + /** + * @deprecated This method is useless because the state of the agent is not + * supposed to evolve before or after its cycle. Use + * OnAgentCycleBegin/End instead. + * + * This method is final because it must not be implemented. + * Implement it will have no effect. + */ + @Deprecated + protected final void onSystemCycleEnd() { + + } + + /** + * This method is called automatically and corresponds to a full cycle of an + * agent + */ + @Override + public void run() { + + ExecutionPolicy executionPolicy = amas.getExecutionPolicy(); + if (executionPolicy == ExecutionPolicy.TWO_PHASES) { + + currentPhase = nextPhase(); + switch (currentPhase) { + case PERCEPTION: + phase1(); + amas.informThatAgentPerceptionIsFinished(); + break; + case DECISION_AND_ACTION: + phase2(); + amas.informThatAgentDecisionAndActionAreFinished(); + break; + default: + Log.defaultLog.fatal("AMAK", "An agent is being run in an invalid phase (%s)", currentPhase); + } + } else if (executionPolicy == ExecutionPolicy.ONE_PHASE) { + onePhaseCycle(); + amas.informThatAgentPerceptionIsFinished(); + amas.informThatAgentDecisionAndActionAreFinished(); + } + } + public void onePhaseCycle() { + currentPhase = Phase.PERCEPTION; + phase1(); + currentPhase = Phase.DECISION_AND_ACTION; + phase2(); + } + /** + * This method represents the perception phase of the agent + */ + protected final void phase1() { + onAgentCycleBegin(); + perceive(); + currentPhase = Phase.PERCEPTION_DONE; + } + + /** + * This method represents the decisionAndAction phase of the agent + */ + protected final void phase2() { + decideAndAct(); + executionOrder = _computeExecutionOrder(); + onExpose(); + if (!Configuration.commandLineMode) + onUpdateRender(); + onAgentCycleEnd(); + currentPhase = Phase.DECISION_AND_ACTION_DONE; + } + + /** + * Determine which phase comes after another + * + * @return the next phase + */ + private Phase nextPhase() { + switch (currentPhase) { + case PERCEPTION_DONE: + return Phase.DECISION_AND_ACTION; + case INITIALIZING: + case DECISION_AND_ACTION_DONE: + default: + return Phase.PERCEPTION; + } + } + + /** + * Compute the execution order from the layer and a random value. This method + * shouldn't be overridden. + * + * @return A number used by amak to determine which agent executes first + */ + protected int _computeExecutionOrder() { + return computeExecutionOrderLayer() * 10000 + amas.getEnvironment().getRandom().nextInt(10000); + } + + /** + * Perceive, decide and act + */ + void perceive() { + for (Agent<A, E> agent : neighborhood) { + criticalities.put(agent, agent.criticality); + } + onPerceive(); + // Criticality of agent should be updated after perception AND after action + criticality = computeCriticality(); + criticalities.put(this, criticality); + } + + /** + * A combination of decision and action as called by the framework + */ + private final void decideAndAct() { + onDecideAndAct(); + + criticality = computeCriticality(); + } + + /** + * Decide and act These two phases can often be grouped + */ + protected void onDecideAndAct() { + onDecide(); + onAct(); + } + + /** + * Convenient method giving the most critical neighbor at a given moment + * + * @param includingMe + * Should the agent also consider its own criticality + * @return the most critical agent + */ + protected final Agent<A, E> getMostCriticalNeighbor(boolean includingMe) { + List<Agent<A, E>> criticalest = new ArrayList<>(); + double maxCriticality = Double.NEGATIVE_INFINITY; + + if (includingMe) { + criticalest.add(this); + maxCriticality = criticalities.getOrDefault(criticalest, Double.NEGATIVE_INFINITY); + } + for (Entry<Agent<A, E>, Double> e : criticalities.entrySet()) { + if (e.getValue() > maxCriticality) { + criticalest.clear(); + maxCriticality = e.getValue(); + criticalest.add(e.getKey()); + } else if (e.getValue() == maxCriticality) { + criticalest.add(e.getKey()); + } + } + if (criticalest.isEmpty()) + return null; + return criticalest.get(getEnvironment().getRandom().nextInt(criticalest.size())); + } + + /** + * Get the latest computed execution order + * + * @return the execution order + */ + public int getExecutionOrder() { + return executionOrder; + } + + /** + * Getter for the AMAS + * + * @return the amas + */ + public A getAmas() { + return amas; + } + + /** + * Remove the agent from the system + */ + public void destroy() { + getAmas()._removeAgent(this); + } + + /** + * Agent toString + */ + @Override + public String toString() { + return String.format("Agent #%d", id); + } + + /** + * Getter for the current phase of the agent + * + * @return the current phase + */ + public Phase getCurrentPhase() { + return currentPhase; + } + + /** + * Return the id of the agent + * + * @return the id of the agent + */ + public int getId() { + return id; + } + + /** + * Getter for the environment + * + * @return the environment + */ + public E getEnvironment() { + return getAmas().getEnvironment(); + } + + public boolean isSynchronous() { + return synchronous ; + } +} diff --git a/AMAKFX/src/fr/irit/smac/amak/Amas.java b/AMAKFX/src/fr/irit/smac/amak/Amas.java index a73d0e5107ef13fc638c7e17037858d93ae44c5d..965306ca9e864c7cab05e8149012192f0e71e70c 100644 --- a/AMAKFX/src/fr/irit/smac/amak/Amas.java +++ b/AMAKFX/src/fr/irit/smac/amak/Amas.java @@ -13,9 +13,11 @@ import java.util.stream.Collectors; import fr.irit.smac.amak.tools.Log; import fr.irit.smac.amak.tools.RunLaterHelper; +import fr.irit.smac.amak.ui.AmasMultiUIWindow; import fr.irit.smac.amak.ui.MainWindow; import fr.irit.smac.amak.ui.SchedulerToolbar; import fr.irit.smac.amak.ui.VUI; +import fr.irit.smac.amak.ui.VUIMulti; /** * This class must be overridden by multi-agent systems @@ -26,6 +28,10 @@ import fr.irit.smac.amak.ui.VUI; * The environment of the MAS */ public class Amas<E extends Environment> implements Schedulable { + + public AmasMultiUIWindow amasMultiUIWindow; + public VUIMulti vuiMulti; + /** * List of agents present in the system */ @@ -155,6 +161,43 @@ public class Amas<E extends Environment> implements Schedulable { this.onRenderingInitialization(); this.scheduler.unlock(); } + + public Amas(AmasMultiUIWindow window, VUIMulti vui, E environment, Scheduling scheduling, Object... params) { + + if(!Configuration.commandLineMode) { + amasMultiUIWindow = window; + vuiMulti = vui; + amasMultiUIWindow.addTabbedPanel(vuiMulti.title, vuiMulti.getPanel()); + } + + + executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(Configuration.allowedSimultaneousAgentsExecution); + + //this.scheduler = environment.getScheduler(); + if (scheduling == Scheduling.DEFAULT) { + + this.scheduler = Scheduler.getDefaultMultiUIScheduler(window); + this.scheduler.add(this); + } else { + this.scheduler = new Scheduler(this); + if (scheduling == Scheduling.UI && !Configuration.commandLineMode) { + amasMultiUIWindow.addToolbar(new SchedulerToolbar("Amas #" + id, getScheduler())); + } + } + + this.scheduler.lock(); + this.params = params; + this.environment = environment; + this.onInitialConfiguration(); + executionPolicy = Configuration.executionPolicy; + this.onInitialAgentsCreation(); + + addPendingAgents(); + this.onReady(); + if (!Configuration.commandLineMode) + this.onRenderingInitialization(); + this.scheduler.unlock(); + } /** * The method in which the rendering initialization should be made. For example, @@ -371,7 +414,19 @@ public class Amas<E extends Environment> implements Schedulable { * {@link Amas#onRenderingInitialization} */ protected void onUpdateRender() { - VUI.get().updateCanvas(); + if(Configuration.multiUI) { + vuiMulti.updateCanvas(); + }else { + VUI.get().updateCanvas(); + } + + } + + + + + public VUIMulti getVUIMulti() { + return vuiMulti; } /** diff --git a/AMAKFX/src/fr/irit/smac/amak/Configuration.java b/AMAKFX/src/fr/irit/smac/amak/Configuration.java index bc8050f8b87e296f7497acf1932764c02508c4f2..34c301cc8ae3334a7c8f49476db23e17af8a6b65 100644 --- a/AMAKFX/src/fr/irit/smac/amak/Configuration.java +++ b/AMAKFX/src/fr/irit/smac/amak/Configuration.java @@ -47,4 +47,6 @@ public class Configuration { * By default AMAK will wait for 1 sec before updating the plots */ public static double plotMilliSecondsUpdate = 1000; + + public static boolean multiUI = false; } diff --git a/AMAKFX/src/fr/irit/smac/amak/Environment.java b/AMAKFX/src/fr/irit/smac/amak/Environment.java index 68433e456f0c7b95e24b646a47e8806d67dd4666..e048ea299ca9fb0433762c93d30b570cca6d19b8 100644 --- a/AMAKFX/src/fr/irit/smac/amak/Environment.java +++ b/AMAKFX/src/fr/irit/smac/amak/Environment.java @@ -1,145 +1,170 @@ -package fr.irit.smac.amak; - -import java.util.Random; - -import fr.irit.smac.amak.ui.MainWindow; -import fr.irit.smac.amak.ui.SchedulerToolbar; - -/** - * This class must be overridden by environments - * - * @author Alexandre Perles - * - */ -public abstract class Environment implements Schedulable { - - /** - * Unique index to give unique id to each environment - */ - private static int uniqueIndex; - - /** - * The id of the environment - */ - private final int id = uniqueIndex++; - - /** - * The parameters that are passed to {@link Environment#onInitialization()} - */ - protected Object[] params; - /** - * Random object common to the amas - */ - private Random random = new Random(); - /** - * The scheduler of the environment - */ - private Scheduler scheduler; - - /** - * Constructor - * - * @param _scheduling - * The scheduling of the environment - * @param params - * The parameters to initialize the environment - */ - public Environment(Scheduling _scheduling, Object... params) { - if (_scheduling == Scheduling.DEFAULT) { - this.scheduler = Scheduler.getDefaultScheduler(); - this.scheduler.add(this); - } else { - this.scheduler = new Scheduler(this); - if (_scheduling == Scheduling.UI && !Configuration.commandLineMode) - MainWindow.addToolbar(new SchedulerToolbar("Environment #" + id, getScheduler())); - } - this.scheduler.lock(); - this.params = params; - onInitialization(); - onInitialEntitiesCreation(); - if (!Configuration.commandLineMode) - onRenderingInitialization(); - this.scheduler.unlock(); - } - - /** - * Override this method is you wish to render environment. For example, you can - * use this method to create a VUI drawable object. - */ - private void onRenderingInitialization() { - } - - /** - * Getter for the scheduler - * - * @return the scheduler - */ - public Scheduler getScheduler() { - return scheduler; - } - - /** - * Set the seed for the common random object. This method should be called at - * the very beginning of the initialization process - * - * @param _seed - * The seed to initialize the random object - */ - public void setSeed(long _seed) { - random = new Random(_seed); - } - - /** - * This method is called during the initialization process of the environment - */ - public void onInitialization() { - } - - /** - * This method is called after the initialization process of the environment to - * create entities - */ - public void onInitialEntitiesCreation() { - } - - /** - * This method is called at each cycle of the environment - */ - public void onCycle() { - } - - @Override - public boolean stopCondition() { - return false; - } - - @Override - public final void cycle() { - onCycle(); - if (!Configuration.commandLineMode) - onUpdateRender(); - } - - /** - * Override this method to update rendering related to the environment - */ - protected void onUpdateRender() { - } - - /** - * Getter for the random object - * - * @return the random object - */ - public Random getRandom() { - return random; - } - - @Override - public void onSchedulingStarts() { - } - - @Override - public void onSchedulingStops() { - } -} +package fr.irit.smac.amak; + +import java.util.Random; + +import fr.irit.smac.amak.ui.AmasMultiUIWindow; +import fr.irit.smac.amak.ui.MainWindow; +import fr.irit.smac.amak.ui.SchedulerToolbar; + +/** + * This class must be overridden by environments + * + * @author Alexandre Perles + * + */ +public abstract class Environment implements Schedulable { + + public AmasMultiUIWindow amasMultiUIWindow; + + /** + * Unique index to give unique id to each environment + */ + private static int uniqueIndex; + + /** + * The id of the environment + */ + private final int id = uniqueIndex++; + + /** + * The parameters that are passed to {@link Environment#onInitialization()} + */ + protected Object[] params; + /** + * Random object common to the amas + */ + private Random random = new Random(); + /** + * The scheduler of the environment + */ + private Scheduler scheduler; + + /** + * Constructor + * + * @param _scheduling + * The scheduling of the environment + * @param params + * The parameters to initialize the environment + */ + public Environment(Scheduling _scheduling, Object... params) { + if (_scheduling == Scheduling.DEFAULT) { + this.scheduler = Scheduler.getDefaultScheduler(); + this.scheduler.add(this); + } else { + this.scheduler = new Scheduler(this); + if (_scheduling == Scheduling.UI && !Configuration.commandLineMode) + MainWindow.addToolbar(new SchedulerToolbar("Environment #" + id, getScheduler())); + } + this.scheduler.lock(); + this.params = params; + onInitialization(); + onInitialEntitiesCreation(); + if (!Configuration.commandLineMode) + onRenderingInitialization(); + this.scheduler.unlock(); + } + + public Environment(AmasMultiUIWindow window, Scheduling _scheduling, Object... params) { + amasMultiUIWindow = window; +// if (_scheduling == Scheduling.DEFAULT) { +// this.scheduler = Scheduler.getDefaultMultiUIScheduler(window); +// this.scheduler.add(this); +// } else { +// this.scheduler = new Scheduler(this); +// if (_scheduling == Scheduling.UI && !Configuration.commandLineMode) +// amasMultiUIWindow.addToolbar(new SchedulerToolbar("Environment #" + id, getScheduler())); +// } +// +// this.scheduler.lock(); + this.params = params; + onInitialization(); + onInitialEntitiesCreation(); + if (!Configuration.commandLineMode) + onRenderingInitialization(); +// this.scheduler.unlock(); + } + + /** + * Override this method is you wish to render environment. For example, you can + * use this method to create a VUI drawable object. + */ + private void onRenderingInitialization() { + } + + /** + * Getter for the scheduler + * + * @return the scheduler + */ + public Scheduler getScheduler() { + return scheduler; + } + + /** + * Set the seed for the common random object. This method should be called at + * the very beginning of the initialization process + * + * @param _seed + * The seed to initialize the random object + */ + public void setSeed(long _seed) { + random = new Random(_seed); + } + + /** + * This method is called during the initialization process of the environment + */ + public void onInitialization() { + } + + /** + * This method is called after the initialization process of the environment to + * create entities + */ + public void onInitialEntitiesCreation() { + } + + /** + * This method is called at each cycle of the environment + */ + public void onCycle() { + } + + @Override + public boolean stopCondition() { + return false; + } + + @Override + public final void cycle() { + onCycle(); + if (!Configuration.commandLineMode) + onUpdateRender(); + } + + /** + * Override this method to update rendering related to the environment + */ + protected void onUpdateRender() { + } + + /** + * Getter for the random object + * + * @return the random object + */ + public Random getRandom() { + return random; + } + + @Override + public void onSchedulingStarts() { + } + + @Override + public void onSchedulingStops() { + } + + +} diff --git a/AMAKFX/src/fr/irit/smac/amak/Scheduler.java b/AMAKFX/src/fr/irit/smac/amak/Scheduler.java index bffeeb2ce6a8d7e7a2422d165b41817e2e8b44c6..2c6a1a18701474de8807f168378aca26f69b8ee8 100644 --- a/AMAKFX/src/fr/irit/smac/amak/Scheduler.java +++ b/AMAKFX/src/fr/irit/smac/amak/Scheduler.java @@ -1,341 +1,357 @@ -package fr.irit.smac.amak; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.ConcurrentModificationException; -import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Queue; -import java.util.Set; -import java.util.concurrent.locks.ReentrantLock; -import java.util.function.Consumer; - -import fr.irit.smac.amak.ui.MainWindow; -import fr.irit.smac.amak.ui.SchedulerToolbar; - -/** - * A scheduler associated to a MAS - * - * @author Alexandre Perles - * - */ -public class Scheduler implements Runnable, Serializable { - /** - * Unique ID meant to handle serialization correctly - */ - private static final long serialVersionUID = -4765899565369100376L; - /** - * The schedulables object handled by the scheduler - */ - private final Set<Schedulable> schedulables = new LinkedHashSet<>(); - /** - * The state of the scheduler {@link State} - */ - private State state; - /** - * The sleep time in ms between each cycle - */ - private int sleep; - /** - * A lock to protect the state - */ - private final ReentrantLock stateLock = new ReentrantLock(); - /** - * Method that is called when the scheduler stops - */ - private Consumer<Scheduler> onStop; - /** - * The methods called when the speed is changed. Useful to change the value of - * the GUI slider of {@link SchedulerToolbar} - */ - private List<Consumer<Scheduler>> onChange = new ArrayList<>(); - /** - * The idea is to prevent scheduler from launching if the schedulables are not - * yet fully ready - */ - private int locked = 0; - /** - * The default scheduler - */ - private static Scheduler defaultScheduler; - /** - * The schedulables that must be added - */ - private Queue<Schedulable> pendingAdditionSchedulables = new LinkedList<>(); - /** - * The schedulables that must be removed - */ - private Queue<Schedulable> pendingRemovalSchedulables = new LinkedList<>(); - - /** - * State of the scheduler - * - */ - public enum State { - /** - * The scheduler is running - */ - RUNNING, - /** - * The scheduler is paused - */ - IDLE, - /** - * The scheduler is expected to stop at the end at the current cycle - */ - PENDING_STOP - - } - - /** - * Constructor which set the initial state and auto start if requested - * - * @param _schedulables - * the corresponding schedulables - */ - public Scheduler(Schedulable... _schedulables) { - - for (Schedulable schedulable : _schedulables) { - this.add(schedulable); - } - this.state = State.IDLE; - } - - /** - * Create or return the default scheduler - * - * @return The default scheduler - */ - public static Scheduler getDefaultScheduler() { - if (defaultScheduler == null) { - defaultScheduler = new Scheduler(); - if (!Configuration.commandLineMode) { - MainWindow.instance(); - SchedulerToolbar st = new SchedulerToolbar("Default", defaultScheduler); - MainWindow.addToolbar(st); - } - } - return defaultScheduler; - } - - /** - * Set the delay between two cycles and launch the scheduler if it is not - * running - * - * @param i - * the delay between two cycles - */ - public void startWithSleep(int i) { - if (locked > 0) { - - synchronized (onChange) { - onChange.forEach(c -> c.accept(this)); - } - return; - } - setSleep(i); - stateLock.lock(); - switch (state) { - case IDLE: - state = State.RUNNING; - new Thread(this).start(); - break; - default: - break; - } - stateLock.unlock(); - synchronized (onChange) { - onChange.forEach(c -> c.accept(this)); - } - } - - /** - * Start (or continue) with no delay between cycles - */ - public void start() { - startWithSleep(Schedulable.DEFAULT_SLEEP); - } - - /** - * Execute one cycle - */ - public void step() { - if (locked > 0) { - synchronized (onChange) { - onChange.forEach(c -> c.accept(this)); - } - return; - } - this.setSleep(0); - stateLock.lock(); - switch (state) { - case IDLE: - state = State.PENDING_STOP; - new Thread(this).start(); - break; - default: - break; - - } - stateLock.unlock(); - synchronized (onChange) { - onChange.forEach(c -> c.accept(this)); - } - } - - /** - * Stop the scheduler if it is running - */ - public void stop() { - stateLock.lock(); - switch (state) { - case RUNNING: - state = State.PENDING_STOP; - break; - default: - break; - - } - stateLock.unlock(); - synchronized (onChange) { - onChange.forEach(c -> c.accept(this)); - } - } - - /** - * Threaded run method - */ - @Override - public void run() { - treatPendingSchedulables(); - for (Schedulable schedulable : schedulables) { - schedulable.onSchedulingStarts(); - } - boolean mustStop; - do { - for (Schedulable schedulable : schedulables) { - schedulable.cycle(); - } - if (getSleep() != 0) { - try { - Thread.sleep(getSleep()); - } catch (final InterruptedException e) { - e.printStackTrace(); - } - } - mustStop = false; - for (Schedulable schedulable : schedulables) { - mustStop |= schedulable.stopCondition(); - } - } while (state == State.RUNNING && !mustStop); - stateLock.lock(); - state = State.IDLE; - stateLock.unlock(); - - for (Schedulable schedulable : schedulables) { - schedulable.onSchedulingStops(); - } - treatPendingSchedulables(); - if (onStop != null) - onStop.accept(this); - } - - /** - * Effectively Add or Remove the schedulables that were added or removed during - * a cycle to avoid {@link ConcurrentModificationException} - */ - private void treatPendingSchedulables() { - while (!pendingAdditionSchedulables.isEmpty()) - schedulables.add(pendingAdditionSchedulables.poll()); - while (!pendingRemovalSchedulables.isEmpty()) - schedulables.remove(pendingRemovalSchedulables.poll()); - - } - - /** - * Set the method that must be executed when the system is stopped - * - * @param _onStop - * Consumer method - */ - public final void setOnStop(Consumer<Scheduler> _onStop) { - this.onStop = _onStop; - } - - /** - * Add a method that must be executed when the scheduler speed is changed - * - * @param _onChange - * Consumer method - */ - public final void addOnChange(Consumer<Scheduler> _onChange) { - synchronized (onChange) { - this.onChange.add(_onChange); - } - } - - /** - * Is the scheduler running ? - * - * @return true if the scheduler is running - */ - public boolean isRunning() { - return state == State.RUNNING; - } - - /** - * Getter for the sleep time - * - * @return the sleep time - */ - - public int getSleep() { - return sleep; - } - - /** - * Setter for the sleep time - * - * @param sleep - * The time between each cycle - */ - public void setSleep(int sleep) { - this.sleep = sleep; - } - - /** - * Plan to add a schedulable - * - * @param _schedulable - * the schedulable to add - */ - public void add(Schedulable _schedulable) { - this.pendingAdditionSchedulables.add(_schedulable); - } - - /** - * Plan to remove a schedulable - * - * @param _schedulable - * the schedulable to remove - */ - public void remove(Schedulable _schedulable) { - this.pendingRemovalSchedulables.add(_schedulable); - } - - /** - * Soft lock the scheduler to avoid a too early running - */ - public void lock() { - locked++; - } - - /** - * Soft unlock the scheduler to avoid a too early running - */ - public void unlock() { - locked--; - } - -} +package fr.irit.smac.amak; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.ConcurrentModificationException; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; + +import fr.irit.smac.amak.ui.AmasMultiUIWindow; +import fr.irit.smac.amak.ui.MainWindow; +import fr.irit.smac.amak.ui.SchedulerToolbar; + +/** + * A scheduler associated to a MAS + * + * @author Alexandre Perles + * + */ +public class Scheduler implements Runnable, Serializable { + /** + * Unique ID meant to handle serialization correctly + */ + private static final long serialVersionUID = -4765899565369100376L; + /** + * The schedulables object handled by the scheduler + */ + private final Set<Schedulable> schedulables = new LinkedHashSet<>(); + /** + * The state of the scheduler {@link State} + */ + private State state; + /** + * The sleep time in ms between each cycle + */ + private int sleep; + /** + * A lock to protect the state + */ + private final ReentrantLock stateLock = new ReentrantLock(); + /** + * Method that is called when the scheduler stops + */ + private Consumer<Scheduler> onStop; + /** + * The methods called when the speed is changed. Useful to change the value of + * the GUI slider of {@link SchedulerToolbar} + */ + private List<Consumer<Scheduler>> onChange = new ArrayList<>(); + /** + * The idea is to prevent scheduler from launching if the schedulables are not + * yet fully ready + */ + private int locked = 0; + /** + * The default scheduler + */ + private static Scheduler defaultScheduler; + + + /** + * The schedulables that must be added + */ + private Queue<Schedulable> pendingAdditionSchedulables = new LinkedList<>(); + /** + * The schedulables that must be removed + */ + private Queue<Schedulable> pendingRemovalSchedulables = new LinkedList<>(); + + /** + * State of the scheduler + * + */ + public enum State { + /** + * The scheduler is running + */ + RUNNING, + /** + * The scheduler is paused + */ + IDLE, + /** + * The scheduler is expected to stop at the end at the current cycle + */ + PENDING_STOP + + } + + /** + * Constructor which set the initial state and auto start if requested + * + * @param _schedulables + * the corresponding schedulables + */ + public Scheduler(Schedulable... _schedulables) { + + for (Schedulable schedulable : _schedulables) { + this.add(schedulable); + } + this.state = State.IDLE; + } + + /** + * Create or return the default scheduler + * + * @return The default scheduler + */ + public static Scheduler getDefaultScheduler() { + if (defaultScheduler == null) { + defaultScheduler = new Scheduler(); + if (!Configuration.commandLineMode) { + MainWindow.instance(); + SchedulerToolbar st = new SchedulerToolbar("Default", defaultScheduler); + MainWindow.addToolbar(st); + } + } + return defaultScheduler; + } + + + + + + public static Scheduler getDefaultMultiUIScheduler(AmasMultiUIWindow window) { + Scheduler multiUIScheduler = new Scheduler(); + if (!Configuration.commandLineMode) { + SchedulerToolbar st = new SchedulerToolbar("Default", multiUIScheduler); + window.addToolbar(st); + } + return multiUIScheduler; + } + + /** + * Set the delay between two cycles and launch the scheduler if it is not + * running + * + * @param i + * the delay between two cycles + */ + public void startWithSleep(int i) { + if (locked > 0) { + + synchronized (onChange) { + onChange.forEach(c -> c.accept(this)); + } + return; + } + setSleep(i); + stateLock.lock(); + switch (state) { + case IDLE: + state = State.RUNNING; + new Thread(this).start(); + break; + default: + break; + } + stateLock.unlock(); + synchronized (onChange) { + onChange.forEach(c -> c.accept(this)); + } + } + + /** + * Start (or continue) with no delay between cycles + */ + public void start() { + startWithSleep(Schedulable.DEFAULT_SLEEP); + } + + /** + * Execute one cycle + */ + public void step() { + if (locked > 0) { + synchronized (onChange) { + onChange.forEach(c -> c.accept(this)); + } + return; + } + this.setSleep(0); + stateLock.lock(); + switch (state) { + case IDLE: + state = State.PENDING_STOP; + new Thread(this).start(); + break; + default: + break; + + } + stateLock.unlock(); + synchronized (onChange) { + onChange.forEach(c -> c.accept(this)); + } + } + + /** + * Stop the scheduler if it is running + */ + public void stop() { + stateLock.lock(); + switch (state) { + case RUNNING: + state = State.PENDING_STOP; + break; + default: + break; + + } + stateLock.unlock(); + synchronized (onChange) { + onChange.forEach(c -> c.accept(this)); + } + } + + /** + * Threaded run method + */ + @Override + public void run() { + treatPendingSchedulables(); + for (Schedulable schedulable : schedulables) { + schedulable.onSchedulingStarts(); + } + boolean mustStop; + do { + for (Schedulable schedulable : schedulables) { + schedulable.cycle(); + } + if (getSleep() != 0) { + try { + Thread.sleep(getSleep()); + } catch (final InterruptedException e) { + e.printStackTrace(); + } + } + mustStop = false; + for (Schedulable schedulable : schedulables) { + mustStop |= schedulable.stopCondition(); + } + } while (state == State.RUNNING && !mustStop); + stateLock.lock(); + state = State.IDLE; + stateLock.unlock(); + + for (Schedulable schedulable : schedulables) { + schedulable.onSchedulingStops(); + } + treatPendingSchedulables(); + if (onStop != null) + onStop.accept(this); + } + + /** + * Effectively Add or Remove the schedulables that were added or removed during + * a cycle to avoid {@link ConcurrentModificationException} + */ + private void treatPendingSchedulables() { + while (!pendingAdditionSchedulables.isEmpty()) + schedulables.add(pendingAdditionSchedulables.poll()); + while (!pendingRemovalSchedulables.isEmpty()) + schedulables.remove(pendingRemovalSchedulables.poll()); + + } + + /** + * Set the method that must be executed when the system is stopped + * + * @param _onStop + * Consumer method + */ + public final void setOnStop(Consumer<Scheduler> _onStop) { + this.onStop = _onStop; + } + + /** + * Add a method that must be executed when the scheduler speed is changed + * + * @param _onChange + * Consumer method + */ + public final void addOnChange(Consumer<Scheduler> _onChange) { + synchronized (onChange) { + this.onChange.add(_onChange); + } + } + + /** + * Is the scheduler running ? + * + * @return true if the scheduler is running + */ + public boolean isRunning() { + return state == State.RUNNING; + } + + /** + * Getter for the sleep time + * + * @return the sleep time + */ + + public int getSleep() { + return sleep; + } + + /** + * Setter for the sleep time + * + * @param sleep + * The time between each cycle + */ + public void setSleep(int sleep) { + this.sleep = sleep; + } + + /** + * Plan to add a schedulable + * + * @param _schedulable + * the schedulable to add + */ + public void add(Schedulable _schedulable) { + this.pendingAdditionSchedulables.add(_schedulable); + } + + /** + * Plan to remove a schedulable + * + * @param _schedulable + * the schedulable to remove + */ + public void remove(Schedulable _schedulable) { + this.pendingRemovalSchedulables.add(_schedulable); + } + + /** + * Soft lock the scheduler to avoid a too early running + */ + public void lock() { + locked++; + } + + /** + * Soft unlock the scheduler to avoid a too early running + */ + public void unlock() { + locked--; + } + +} diff --git a/AMAKFX/src/fr/irit/smac/amak/examples/asyncrandomants/AsyncAntsLaunchExample.java b/AMAKFX/src/fr/irit/smac/amak/examples/asyncrandomants/AsyncAntsLaunchExample.java deleted file mode 100644 index 7b295f64e71a469ace234de3e827fee863546929..0000000000000000000000000000000000000000 --- a/AMAKFX/src/fr/irit/smac/amak/examples/asyncrandomants/AsyncAntsLaunchExample.java +++ /dev/null @@ -1,40 +0,0 @@ -package fr.irit.smac.amak.examples.asyncrandomants; - -import fr.irit.smac.amak.Configuration; -import fr.irit.smac.amak.examples.randomants.AntExample; -import fr.irit.smac.amak.examples.randomants.AntHillExample; -import fr.irit.smac.amak.examples.randomants.WorldExample; -import fr.irit.smac.amak.ui.MainWindow; -import javafx.scene.control.Label; -import javafx.scene.layout.Pane; - -/** - * Class aiming at starting the mas-less ants system - * - * @author perles - * - */ -public class AsyncAntsLaunchExample { - - /** - * Launch method - * - * @param args - * Main arguments - */ - public static void main(String[] args) { - Configuration.allowedSimultaneousAgentsExecution = 4; - WorldExample env = new WorldExample(); - AntHillExample amas = new AntHillExample(env); - for (int i = 0; i < 50; i++) - new AntExample(amas, 0, 0); - - Pane panel = new Pane(); - String content = "Async AntHill simulation\n\n" + "Ants move randomly.\n" - + "This demo is here to show AMAK asynchronous agent capacities."; - Label label = new Label(content); - label.setStyle("-fx-font-weight: bold;"); - panel.getChildren().add(label); - MainWindow.setLeftPanel(panel); - } -} diff --git a/AMAKFX/src/fr/irit/smac/amak/examples/asyncrandomants/package-info.java b/AMAKFX/src/fr/irit/smac/amak/examples/asyncrandomants/package-info.java deleted file mode 100644 index 789de13bcda89c7b064448c0c987cb26fe666abd..0000000000000000000000000000000000000000 --- a/AMAKFX/src/fr/irit/smac/amak/examples/asyncrandomants/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * This package contains an example showing ants moving randomly. - * In this example, agents are fully asynchronous. - */ -package fr.irit.smac.amak.examples.asyncrandomants; diff --git a/AMAKFX/src/fr/irit/smac/amak/examples/randomants/AntExample.java b/AMAKFX/src/fr/irit/smac/amak/examples/randomants/AntExample.java index 8186b0d4b27f736e337391f0110cb3340d7b2d2d..9a847c638d781660d25ca8c7f246d6fea72d7116 100644 --- a/AMAKFX/src/fr/irit/smac/amak/examples/randomants/AntExample.java +++ b/AMAKFX/src/fr/irit/smac/amak/examples/randomants/AntExample.java @@ -1,88 +1,88 @@ -package fr.irit.smac.amak.examples.randomants; - -import fr.irit.smac.amak.Agent; -import fr.irit.smac.amak.ui.VUI; -import fr.irit.smac.amak.ui.drawables.DrawableImage; - -public class AntExample extends Agent<AntHillExample, WorldExample> { - - private boolean dead = false; - - /** - * X coordinate of the ant in the world - */ - public double dx; - /** - * Y coordinate of the ant in the world - */ - public double dy; - /** - * Angle in radians - */ - private double angle = Math.random() * Math.PI * 2; - private DrawableImage image; - - /** - * Constructor of the ant - * - * @param amas - * the amas the ant belongs to - * @param startX - * Initial X coordinate - * @param startY - * Initial Y coordinate - */ - public AntExample(AntHillExample amas, double startX, double startY) { - super(amas, startX, startY); - } - @Override - public void onInitialization() { - dx = (double) params[0]; - dy = (double) params[1]; - } - - @Override - protected void onRenderingInitialization() { - image = VUI.get().createAndAddImage(dx, dy, "file:resources/ant.png"); - image.setName("Ant "+getId()); - } - - /** - * Move in a random direction - */ - @Override - protected void onDecideAndAct() { - double random = amas.getEnvironment().getRandom().nextGaussian(); - angle += random * 0.1; - dx += Math.cos(angle); - dy += Math.sin(angle); - while (dx >= getAmas().getEnvironment().getWidth() / 2) - dx -= getAmas().getEnvironment().getWidth(); - while (dy >= getAmas().getEnvironment().getHeight() / 2) - dy -= getAmas().getEnvironment().getHeight(); - while (dx < -getAmas().getEnvironment().getWidth() / 2) - dx += getAmas().getEnvironment().getWidth(); - while (dy < -getAmas().getEnvironment().getHeight() / 2) - dy += getAmas().getEnvironment().getHeight(); - - if (amas.getEnvironment().getRandom().nextDouble() < 0.001) { - dead = true; - destroy(); - } - - if (amas.getEnvironment().getRandom().nextDouble() < 0.001) { - new AntExample(getAmas(), dx, dy); - } - } - - @Override - public void onUpdateRender() { - image.move(dx, dy); - image.setAngle(angle); - image.setInfo("Ant "+getId()+"\nPosition "+dx+" "+dy+"\nAngle "+angle); - if(dead) { - image.setFilename("file:Resources/ant_dead.png"); - image.setInfo("Ant "+getId()+"\nPosition "+dx+" "+dy+"\nAngle "+angle+"\nDead"); - } - } -} +package fr.irit.smac.amak.examples.randomants; + +import fr.irit.smac.amak.Agent; +import fr.irit.smac.amak.ui.VUI; +import fr.irit.smac.amak.ui.drawables.DrawableImage; + +public class AntExample extends Agent<AntHillExample, WorldExample> { + + private boolean dead = false; + + /** + * X coordinate of the ant in the world + */ + public double dx; + /** + * Y coordinate of the ant in the world + */ + public double dy; + /** + * Angle in radians + */ + private double angle = Math.random() * Math.PI * 2; + private DrawableImage image; + + /** + * Constructor of the ant + * + * @param amas + * the amas the ant belongs to + * @param startX + * Initial X coordinate + * @param startY + * Initial Y coordinate + */ + public AntExample(AntHillExample amas, double startX, double startY) { + super(amas, startX, startY); + } + @Override + public void onInitialization() { + dx = (double) params[0]; + dy = (double) params[1]; + } + + @Override + protected void onRenderingInitialization() { + image = VUI.get().createAndAddImage(dx, dy, "file:resources/ant.png"); + image.setName("Ant "+getId()); + } + + /** + * Move in a random direction + */ + @Override + protected void onDecideAndAct() { + double random = amas.getEnvironment().getRandom().nextGaussian(); + angle += random * 0.1; + dx += Math.cos(angle); + dy += Math.sin(angle); + while (dx >= getAmas().getEnvironment().getWidth() / 2) + dx -= getAmas().getEnvironment().getWidth(); + while (dy >= getAmas().getEnvironment().getHeight() / 2) + dy -= getAmas().getEnvironment().getHeight(); + while (dx < -getAmas().getEnvironment().getWidth() / 2) + dx += getAmas().getEnvironment().getWidth(); + while (dy < -getAmas().getEnvironment().getHeight() / 2) + dy += getAmas().getEnvironment().getHeight(); + + if (amas.getEnvironment().getRandom().nextDouble() < 0.001) { + dead = true; + destroy(); + } + + if (amas.getEnvironment().getRandom().nextDouble() < 0.001) { + new AntExample(getAmas(), dx, dy); + } + } + + @Override + public void onUpdateRender() { + image.move(dx, dy); + image.setAngle(angle); + image.setInfo("Ant "+getId()+"\nPosition "+dx+" "+dy+"\nAngle "+angle); + if(dead) { + image.setFilename("file:Resources/ant_dead.png"); + image.setInfo("Ant "+getId()+"\nPosition "+dx+" "+dy+"\nAngle "+angle+"\nDead"); + } + } +} diff --git a/AMAKFX/src/fr/irit/smac/amak/examples/randomants/WorldExample.java b/AMAKFX/src/fr/irit/smac/amak/examples/randomants/WorldExample.java index 2c6ea56a9ef181a9cf191d87669c578c3dd7e24b..88156754ae2aaa969ed1b24c99ad7143f3854987 100644 --- a/AMAKFX/src/fr/irit/smac/amak/examples/randomants/WorldExample.java +++ b/AMAKFX/src/fr/irit/smac/amak/examples/randomants/WorldExample.java @@ -1,28 +1,28 @@ -package fr.irit.smac.amak.examples.randomants; - -import fr.irit.smac.amak.Environment; -import fr.irit.smac.amak.Scheduling; - -public class WorldExample extends Environment { - public WorldExample(Object...params) { - super(Scheduling.DEFAULT, params); - } - - private int width; - private int height; - - public int getWidth() { - return width; - } - - public int getHeight() { - return height; - } - - @Override - public void onInitialization() { - this.width = 800; - this.height = 600; - } - -} +package fr.irit.smac.amak.examples.randomants; + +import fr.irit.smac.amak.Environment; +import fr.irit.smac.amak.Scheduling; + +public class WorldExample extends Environment { + public WorldExample(Object...params) { + super(Scheduling.DEFAULT, params); + } + + private int width; + private int height; + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + @Override + public void onInitialization() { + this.width = 800; + this.height = 600; + } + +} diff --git a/AMAKFX/src/fr/irit/smac/amak/examples/randomantsMultiUi/AntExampleMutliUI.java b/AMAKFX/src/fr/irit/smac/amak/examples/randomantsMultiUi/AntExampleMutliUI.java new file mode 100644 index 0000000000000000000000000000000000000000..a8b731ca2be36cc141828375e17721adcf89ea4e --- /dev/null +++ b/AMAKFX/src/fr/irit/smac/amak/examples/randomantsMultiUi/AntExampleMutliUI.java @@ -0,0 +1,92 @@ +package fr.irit.smac.amak.examples.randomantsMultiUi; + +import fr.irit.smac.amak.Agent; +import fr.irit.smac.amak.ui.AmasMultiUIWindow; +import fr.irit.smac.amak.ui.VUI; +import fr.irit.smac.amak.ui.drawables.DrawableImage; + +public class AntExampleMutliUI extends Agent<AntHillExampleMultiUI, WorldExampleMultiUI> { + + + + private boolean dead = false; + + /** + * X coordinate of the ant in the world + */ + public double dx; + /** + * Y coordinate of the ant in the world + */ + public double dy; + /** + * Angle in radians + */ + private double angle = Math.random() * Math.PI * 2; + private DrawableImage image; + + /** + * Constructor of the ant + * + * @param amas + * the amas the ant belongs to + * @param startX + * Initial X coordinate + * @param startY + * Initial Y coordinate + */ + public AntExampleMutliUI(AmasMultiUIWindow window, AntHillExampleMultiUI amas, double startX, double startY) { + super(window, amas, startX, startY); + } + @Override + public void onInitialization() { + dx = (double) params[0]; + dy = (double) params[1]; + } + + @Override + protected void onRenderingInitialization() { + image = getAmas().getVUIMulti().createAndAddImage(dx, dy, "file:resources/ant.png"); + image.setName("Ant "+getId()); + } + + /** + * Move in a random direction + */ + @Override + protected void onDecideAndAct() { + + double random = amas.getEnvironment().getRandom().nextGaussian(); + angle += random * 0.1; + dx += Math.cos(angle); + dy += Math.sin(angle); + while (dx >= getAmas().getEnvironment().getWidth() / 2) + dx -= getAmas().getEnvironment().getWidth(); + while (dy >= getAmas().getEnvironment().getHeight() / 2) + dy -= getAmas().getEnvironment().getHeight(); + while (dx < -getAmas().getEnvironment().getWidth() / 2) + dx += getAmas().getEnvironment().getWidth(); + while (dy < -getAmas().getEnvironment().getHeight() / 2) + dy += getAmas().getEnvironment().getHeight(); + + if (amas.getEnvironment().getRandom().nextDouble() < 0.001) { + dead = true; + destroy(); + } + + if (amas.getEnvironment().getRandom().nextDouble() < 0.001) { + new AntExampleMutliUI(getAmas().amasMultiUIWindow, getAmas(), dx, dy); + } + } + + @Override + public void onUpdateRender() { + image.move(dx, dy); + image.setAngle(angle); + image.setInfo("Ant "+getId()+"\nPosition "+dx+" "+dy+"\nAngle "+angle); + if(dead) { + image.setFilename("file:Resources/ant_dead.png"); + image.setInfo("Ant "+getId()+"\nPosition "+dx+" "+dy+"\nAngle "+angle+"\nDead"); + } + } +} diff --git a/AMAKFX/src/fr/irit/smac/amak/examples/randomantsMultiUi/AntHillExampleMultiUI.java b/AMAKFX/src/fr/irit/smac/amak/examples/randomantsMultiUi/AntHillExampleMultiUI.java new file mode 100644 index 0000000000000000000000000000000000000000..344561afb24ba67df1e4fa1031eab902844ff01f --- /dev/null +++ b/AMAKFX/src/fr/irit/smac/amak/examples/randomantsMultiUi/AntHillExampleMultiUI.java @@ -0,0 +1,37 @@ +package fr.irit.smac.amak.examples.randomantsMultiUi; + +import fr.irit.smac.amak.Amas; +import fr.irit.smac.amak.Scheduling; +import fr.irit.smac.amak.tools.RunLaterHelper; +import fr.irit.smac.amak.ui.AmasMultiUIWindow; +import fr.irit.smac.amak.ui.VUI; +import fr.irit.smac.amak.ui.VUIMulti; +import fr.irit.smac.amak.ui.drawables.DrawableString; + +public class AntHillExampleMultiUI extends Amas<WorldExampleMultiUI> { + + private DrawableString antsCountLabel; + + public AntHillExampleMultiUI(AmasMultiUIWindow window, VUIMulti vui, WorldExampleMultiUI env) { + super(window, vui, env, Scheduling.DEFAULT); + } + + @Override + protected void onRenderingInitialization() { + vuiMulti.createAndAddImage(20, 20, "file:Resources/ant.png").setFixed().setLayer(10).setShowInExplorer(false); + antsCountLabel = (DrawableString) vuiMulti.createAndAddString(45, 25, "Ants count").setFixed().setLayer(10).setShowInExplorer(false); + } + + @Override + protected void onInitialAgentsCreation() { + for (int i = 0; i < 50; i++) { + new AntExampleMutliUI(amasMultiUIWindow, this, 0, 0); + } + + } + + @Override + protected void onSystemCycleEnd() { + RunLaterHelper.runLater(()->antsCountLabel.setText("Ants count: " + getAgents().size())); + } +} diff --git a/AMAKFX/src/fr/irit/smac/amak/examples/randomantsMultiUi/AntsLaunchExampleMultiUI.java b/AMAKFX/src/fr/irit/smac/amak/examples/randomantsMultiUi/AntsLaunchExampleMultiUI.java new file mode 100644 index 0000000000000000000000000000000000000000..9fec860bf0a96823e3bebd1851b17bf05cad77a6 --- /dev/null +++ b/AMAKFX/src/fr/irit/smac/amak/examples/randomantsMultiUi/AntsLaunchExampleMultiUI.java @@ -0,0 +1,111 @@ +package fr.irit.smac.amak.examples.randomantsMultiUi; + +import fr.irit.smac.amak.Configuration; +import fr.irit.smac.amak.ui.AmasMultiUIWindow; +import fr.irit.smac.amak.ui.MainWindow; +import fr.irit.smac.amak.ui.VUIMulti; +import javafx.application.Application; +import javafx.application.Platform; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.Slider; +import javafx.scene.layout.Pane; +import javafx.stage.Stage; + +public class AntsLaunchExampleMultiUI extends Application{ + + + public static void main (String[] args) { + + + Application.launch(args); + + + } + + @Override + public void start(Stage primaryStage) throws Exception { + + Configuration.multiUI=true; + Configuration.commandLineMode =false; + + + AmasMultiUIWindow window = new AmasMultiUIWindow("Random Ants Multi UI 1"); + AmasMultiUIWindow window2 = new AmasMultiUIWindow("Random Ants Multi UI 2"); + + + WorldExampleMultiUI env = new WorldExampleMultiUI(window); + WorldExampleMultiUI env2 = new WorldExampleMultiUI(window2); + + + AntHillExampleMultiUI ants = new AntHillExampleMultiUI(window, new VUIMulti("Ants VUI 1"), env); + AntHillExampleMultiUI ants2 = new AntHillExampleMultiUI(window2, new VUIMulti("Ants VUI 2"), env2); + + startTask(ants, 500, 10); + + startTask(ants2, 250, 30); + + + + + } + + public void startTask(AntHillExampleMultiUI amas, long wait, int cycles) + { + // Create a Runnable + Runnable task = new Runnable() + { + public void run() + { + runTask(amas, wait, cycles); + } + }; + + // Run the task in a background thread + Thread backgroundThread = new Thread(task); + // Terminate the running thread if the application exits + backgroundThread.setDaemon(true); + // Start the thread + backgroundThread.start(); + } + + public void runTask(AntHillExampleMultiUI amas, long wait, int cycles) + { + for(int i = 0; i < cycles; i++) + { + try + { + // Get the Status + final String status = "Processing " + i + " of " + cycles; + + // Update the Label on the JavaFx Application Thread + Platform.runLater(new Runnable() + { + @Override + public void run() + { + amas.cycle(); + System.out.println(status); + } + }); + + Thread.sleep(wait); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + } + } + + + @Override + public void stop() throws Exception { + super.stop(); + System.exit(0); + } +} diff --git a/AMAKFX/src/fr/irit/smac/amak/examples/randomantsMultiUi/WorldExampleMultiUI.java b/AMAKFX/src/fr/irit/smac/amak/examples/randomantsMultiUi/WorldExampleMultiUI.java new file mode 100644 index 0000000000000000000000000000000000000000..ffb5c0f1f034f6826152b0bcdd10c42356c5a186 --- /dev/null +++ b/AMAKFX/src/fr/irit/smac/amak/examples/randomantsMultiUi/WorldExampleMultiUI.java @@ -0,0 +1,29 @@ +package fr.irit.smac.amak.examples.randomantsMultiUi; + +import fr.irit.smac.amak.Environment; +import fr.irit.smac.amak.Scheduling; +import fr.irit.smac.amak.ui.AmasMultiUIWindow; + +public class WorldExampleMultiUI extends Environment { + public WorldExampleMultiUI(AmasMultiUIWindow window, Object...params) { + super(window, Scheduling.DEFAULT, params); + } + + private int width; + private int height; + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + @Override + public void onInitialization() { + this.width = 800; + this.height = 600; + } + +} diff --git a/AMAKFX/src/fr/irit/smac/amak/ui/AmakPlot.java b/AMAKFX/src/fr/irit/smac/amak/ui/AmakPlot.java index 60a58286244634a53702a0cea0126b6a792c29aa..a50dcea052955e308ffca135ce65c6d0998898a2 100644 --- a/AMAKFX/src/fr/irit/smac/amak/ui/AmakPlot.java +++ b/AMAKFX/src/fr/irit/smac/amak/ui/AmakPlot.java @@ -36,6 +36,10 @@ public class AmakPlot { public static void add(AmakPlot chart) { MainWindow.addTabbedPanel(chart.name, new ChartViewer(chart.chart)); } + + public static void add(AmasMultiUIWindow window, AmakPlot chart) { + window.addTabbedPanel(chart.name, new ChartViewer(chart.chart)); + } /* ----- */ private String name; @@ -119,6 +123,79 @@ public class AmakPlot { this(name, chart, true); } + + /** + * Create a chart + * @param name the name of the chart, used as the tab name. + * @param chartType {@link ChartType#LINE} or {@link ChartType#BAR} + * @param xAxisLabel label for the x (horizontal) axis + * @param yAxisLabel label for the y (vertical) axis + * @param autoAdd automatically make an {@link AmakPlot#add(AmakPlot)} call ? + */ + public AmakPlot(AmasMultiUIWindow window, String name, ChartType chartType, String xAxisLabel, String yAxisLabel, boolean autoAdd) { + this.name = name; + seriesCollection = new XYSeriesCollection(); + switch (chartType) { + case BAR: + chart = ChartFactory.createXYBarChart(name, xAxisLabel, false, yAxisLabel, seriesCollection); + break; + case LINE: + chart = ChartFactory.createXYLineChart(name, xAxisLabel, yAxisLabel, seriesCollection); + if(useSamplingRenderer) { + chart.getXYPlot().setRenderer(new SamplingXYLineRenderer()); + } + XYPlot plot = (XYPlot)chart.getPlot(); + plot.setDomainGridlinesVisible(true); + plot.setDomainGridlinePaint(Color.lightGray); + plot.setRangeGridlinePaint(Color.lightGray); + break; + default: + System.err.println("AmakPlot : unknow ChartType \""+chartType+"\"."); + break; + } + chart.setAntiAlias(false); + chart.getPlot().setBackgroundPaint(Color.WHITE); + if(autoAdd) { + add(window, this); + } + } + + /** + * Create a chart and add it to the main window. + * @param name the name of the chart, used as the tab name. + * @param chartType {@link ChartType#LINE} or {@link ChartType#BAR} + * @param xAxisLabel label for the x (horizontal) axis + * @param yAxisLabel label for the y (vertical) axis + */ + public AmakPlot(AmasMultiUIWindow window, String name, ChartType chartType, String xAxisLabel, String yAxisLabel) { + this(window, name, chartType, xAxisLabel, yAxisLabel, true); + } + + + /** + * Create a chart out of a JFreeChart. + * Make sure that your chart use an {@link XYSeriesCollection} as dataset. + * @param name the name of the chart, used as the tab name. + * @param chart the {@link JFreeChart} using a {@link XYSeriesCollection} for dataset. + * @param autoAdd automatically make an {@link AmakPlot#add(AmakPlot)} call ? + */ + public AmakPlot(AmasMultiUIWindow window, String name, JFreeChart chart, boolean autoAdd) { + this.name = name; + this.seriesCollection = (XYSeriesCollection) chart.getXYPlot().getDataset(); + this.chart = chart; + add(window, this); + } + + /** + * Create a chart out of a JFreeChart and add it to the main window. + * Make sure that your chart use an {@link XYSeriesCollection} as dataset. + * @param name the name of the chart, used as the tab name. + * @param chart the {@link JFreeChart} using a {@link XYSeriesCollection} for dataset. + */ + public AmakPlot(AmasMultiUIWindow window, String name, JFreeChart chart) { + this(window, name, chart, true); + } + public String getName() { return name; } diff --git a/AMAKFX/src/fr/irit/smac/amak/ui/AmasMultiUIWindow.java b/AMAKFX/src/fr/irit/smac/amak/ui/AmasMultiUIWindow.java new file mode 100644 index 0000000000000000000000000000000000000000..c9519a5c743659f6affa4b5a278d1659ffd7eda8 --- /dev/null +++ b/AMAKFX/src/fr/irit/smac/amak/ui/AmasMultiUIWindow.java @@ -0,0 +1,252 @@ +package fr.irit.smac.amak.ui; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import javax.management.InstanceAlreadyExistsException; + +import fr.irit.smac.amak.Information; +import fr.irit.smac.amak.tools.RunLaterHelper; +import javafx.application.Application; +import javafx.application.Platform; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.scene.Node; +import javafx.scene.Scene; +import javafx.scene.control.Menu; +import javafx.scene.control.MenuBar; +import javafx.scene.control.MenuItem; +import javafx.scene.control.Tab; +import javafx.scene.control.TabPane; +import javafx.scene.control.ToolBar; +import javafx.scene.image.Image; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; +import javafx.stage.WindowEvent; + +/** + * This window is the main one of an AMAS developed using AMAK. It contains a + * toolbar panel and various spaces for panels + * + * @author of the original version (the Swing one) Alexandre Perles, Marcillaud + * Guilhem + * + */ +public class AmasMultiUIWindow extends Stage{ +// /** +// * The window itself +// */ +// public Stage stage; + /** + * The panel which contains the toolbar + */ + public ToolBar toolbarPanel; + + /** + * The main pane of AMAK + */ + public BorderPane organizationPane; + + /** + * The menu bar of the window + */ + public MenuBar menuBar; + /** + * The menus + */ + public HashMap<String, Menu> menus = new HashMap<String, Menu>(); + /** + * The panel in which panels with tab can be added + */ + public TabPane tabbedPanel; + + + + /** + * Create the frame. + * + * @throws InstanceAlreadyExistsException + * if the MainWindow has already been instantiated. This constructor + * should be used by the Application of JavaFX only. + */ + public AmasMultiUIWindow(String title) { + + RunLaterHelper.runLater(() -> { + + VBox root = new VBox(); + + // Creation of the menu bar (Top) + menuBar = new MenuBar(); + root.getChildren().add(menuBar); + + // Border organization + organizationPane = new BorderPane(); + organizationPane.setMinSize(200, 200); //that way we avoid 0 size, which can cause problems + root.getChildren().add(organizationPane); + VBox.setVgrow(organizationPane, Priority.ALWAYS); + + // Creation of scene + this.setTitle(title); + Scene scene = new Scene(root, 450, 300); + //stage = primaryStage; + this.setScene(scene); + this.setOnCloseRequest(new EventHandler<WindowEvent>() { + @Override + public void handle(WindowEvent event) { + Platform.exit(); + } + }); + + // Creation of the toolbar (Bottom) + toolbarPanel = new ToolBar(); + organizationPane.setBottom(toolbarPanel); + + // Creation of the right part of the split pane (Center Right) + tabbedPanel = new TabPane(); + organizationPane.setCenter(tabbedPanel); + + // Creation of the close menu item + MenuItem menuItem = new MenuItem("Close"); + menuItem.setOnAction(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent event) { + System.exit(0); + } + }); + addToMenu("Options", menuItem); + + menuBar.getMenus().add(new Menu("AMAKFX v" + Information.VERSION)); + + this.show(); + + }); + + } + +// @Override +// public void start(Stage primaryStage) throws Exception { +// +// } + + /** + * Add an action when the JavaFX app close. + * + * @param onClose + * The action to be executed when the window is closed + */ + public static void addOnCloseAction(Runnable onClose) { + Runtime.getRuntime().addShutdownHook(new Thread() { + public void run() { onClose.run(); } + }); + } + +// @Override +// public void stop() throws Exception { +// super.stop(); +// System.exit(0); +// } + + /** + * Change the icon of the window + * + * @param filename + * The filename of the icon + */ + public void setWindowIcon(String filename) { + RunLaterHelper.runLater(() -> this.getIcons().add(new Image(filename))); + } + + /** + * Change the title of the main window + * + * @param title + * The new title + */ + public void setWindowTitle(String title) { + RunLaterHelper.runLater(() -> this.setTitle(title)); + } + + /** + * Add a button in the menu options + * + * @param title + * The title of the button + * @param event + * The action to be executed + */ + public void addOptionsItem(String title, EventHandler<ActionEvent> event) { + MenuItem menuItem = new MenuItem(title); + menuItem.setOnAction(event); + RunLaterHelper.runLater(() -> addToMenu("Options", menuItem)); + } + + /** + * Add a tool in the toolbar. + * + * @param tool + */ + public void addToolbar(Node tool) { + RunLaterHelper.runLater(() -> toolbarPanel.getItems().add(tool)); + } + + /** + * Set a panel to the left + * + * @param panel + * The panel + */ + public void setLeftPanel(Node panel) { + RunLaterHelper.runLater(() -> organizationPane.setLeft(panel)); + } + + /** + * Set a panel to the right + * + * @param panel + * The panel + */ + public void setRightPanel(Node panel) { + RunLaterHelper.runLater(() -> organizationPane.setRight(panel)); + } + + /** + * Return the unique instance of MainWindow, may create it. + * + * @return instance + */ + + + + + /** + * Add a panel with a tab + * + * @param title + * The title of the tab + * @param panel + * The panel to add + */ + public void addTabbedPanel(String title, Node panel) { + Tab t = new DraggableTab(title, panel); + RunLaterHelper.runLater(() -> tabbedPanel.getTabs().add(t)); + } + + /** + * Add a {@link MenuItem} to a {@link Menu}. May create the menu and add it to the menu bar. + * @param menuName the name of the menu where the item will be added. + * @param item the item to be added. + */ + public void addToMenu(String menuName, MenuItem item) { + //instance(); + if( !menus.containsKey(menuName) ) { + Menu m = new Menu(menuName); + menus.put(menuName,m); + RunLaterHelper.runLater(() -> menuBar.getMenus().add(m)); + } + RunLaterHelper.runLater(() -> menus.get(menuName).getItems().add(item)); + } +} \ No newline at end of file diff --git a/AMAKFX/src/fr/irit/smac/amak/ui/VUI.java b/AMAKFX/src/fr/irit/smac/amak/ui/VUI.java index 136cb472ac33fbffb9e2f7c7ddda551fe2384d81..2037681065b7736b2c48bb4eea8348a843194faf 100644 --- a/AMAKFX/src/fr/irit/smac/amak/ui/VUI.java +++ b/AMAKFX/src/fr/irit/smac/amak/ui/VUI.java @@ -1,577 +1,579 @@ -package fr.irit.smac.amak.ui; - -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.Semaphore; -import java.util.concurrent.locks.ReentrantLock; - -import fr.irit.smac.amak.tools.RunLaterHelper; -import fr.irit.smac.amak.ui.drawables.Drawable; -import fr.irit.smac.amak.ui.drawables.DrawableImage; -import fr.irit.smac.amak.ui.drawables.DrawablePoint; -import fr.irit.smac.amak.ui.drawables.DrawableRectangle; -import fr.irit.smac.amak.ui.drawables.DrawableString; -import javafx.event.ActionEvent; -import javafx.event.EventHandler; -import javafx.geometry.Insets; -import javafx.scene.control.Button; -import javafx.scene.control.Label; -import javafx.scene.control.ToolBar; -import javafx.scene.control.Tooltip; -import javafx.scene.input.MouseEvent; -import javafx.scene.input.ScrollEvent; -import javafx.scene.layout.Background; -import javafx.scene.layout.BackgroundFill; -import javafx.scene.layout.BorderPane; -import javafx.scene.layout.CornerRadii; -import javafx.scene.layout.Pane; -import javafx.scene.paint.Color; -import javafx.scene.shape.Rectangle; -import javafx.scene.text.TextAlignment; - -/** - * - * Vectorial UI: This class allows to create dynamic rendering with zoom and - * move capacities - * - * @author of original version (the Swing one) perles - * - */ -public class VUI { - /** - * The toolbar of the VUI. - */ - public ToolBar toolbar; - - /** - * The VUI explorer. - * @see VuiExplorer - */ - private VuiExplorer vuiExplorer; - - /** - * List of objects currently being drawn by the VUI - */ - private List<Drawable> drawables = new LinkedList<>(); - /** - * Lock to avoid concurrent modification on the list {@link #drawables} - */ - private ReentrantLock drawablesLock = new ReentrantLock(); - - /** - * A static map to facilitate access to different instances of VUI - */ - private static Map<String, VUI> instances = new HashMap<>(); - - /** - * The horizontal offset of the drawing zone. Used to allow the user to move the - * view. - */ - private double worldOffsetX; - - /** - * The vertical offset of the drawing zone. Used to allow the user to move the - * view. - */ - private double worldOffsetY; - - /** - * The last horizontal position of the mouse when dragging - */ - protected Double lastDragX; - - /** - * The last vertical position of the mouse when dragging - */ - protected Double lastDragY; - - /** - * The main panel of the VUI - */ - private BorderPane panel; - - /** - * The canvas on which all is drawn - */ - private Pane canvas; - - /** - * Label aiming at showing information about the VUI (zoom and offset) - */ - private Label statusLabel; - - /** - * The default value of the {@link #zoom} - */ - private double defaultZoom = 100; - /** - * The default horizontal position of the view - */ - private double defaultWorldCenterX = 0; - /** - * The default vertical position of the view - */ - private double defaultWorldCenterY = 0; - /** - * The value of the zoom. 100 means 1/1 scale - */ - protected double zoom = defaultZoom; - - /** - * The horizontal position of the view - */ - private double worldCenterX = defaultWorldCenterX; - - /** - * The vertical position of the view - */ - private double worldCenterY = defaultWorldCenterY; - - /** - * Used to be sure that only one thread at the same time create a VUI - */ - private static ReentrantLock instanceLock = new ReentrantLock(); - - /** - * Get the default VUI - * - * @return the default VUI - */ - public static VUI get() { - if(!instances.containsKey("Default")) - MainWindow.addTabbedPanel("Default VUI", get("Default").getPanel()); - return get("Default"); - } - - /** - * Create or get a VUI.<br/> - * You have add its panel to the MainWindow yourself. - * - * @param id - * The unique id of the VUI - * @return The VUI with id "id" - */ - public static VUI get(String id) { - instanceLock.lock(); - if (!instances.containsKey(id)) { - VUI value = new VUI(id); - instances.put(id, value); - instanceLock.unlock(); - return value; - } - instanceLock.unlock(); - return instances.get(id); - } - - /** - * Constructor of the VUI. This one is private as it can only be created through - * static method. - * - * @param title - * The title used for the vui - */ - private VUI(String title) { - Semaphore done = new Semaphore(0); - RunLaterHelper.runLater(() -> { - panel = new BorderPane(); - - toolbar = new ToolBar(); - statusLabel = new Label("status"); - statusLabel.setTextAlignment(TextAlignment.LEFT); - toolbar.getItems().add(statusLabel); - panel.setBottom(toolbar); - - Button resetButton = new Button("Reset"); - resetButton.setOnAction(new EventHandler<ActionEvent>() { - @Override - public void handle(ActionEvent event) { - zoom = defaultZoom; - worldCenterX = defaultWorldCenterX; - worldCenterY = defaultWorldCenterY; - updateCanvas(); - } - }); - toolbar.getItems().add(resetButton); - - canvas = new Pane(); - canvas.setBackground(new Background(new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, Insets.EMPTY))); - // clip the canvas (avoid drawing outside of it) - Rectangle clip = new Rectangle(0, 0, 0, 0); - clip.widthProperty().bind(canvas.widthProperty()); - clip.heightProperty().bind(canvas.heightProperty()); - canvas.setClip(clip); - - canvas.setOnMousePressed(new EventHandler<MouseEvent>() { - @Override - public void handle(MouseEvent event) { - lastDragX = event.getX(); - lastDragY = event.getY(); - } - }); - canvas.setOnMouseExited(new EventHandler<MouseEvent>() { - @Override - public void handle(MouseEvent event) { - lastDragX = null; - lastDragY = null; - } - }); - canvas.setOnMouseDragged(new EventHandler<MouseEvent>() { - @Override - public void handle(MouseEvent event) { - try { - double transX = screenToWorldDistance(event.getX() - lastDragX); - double transY = screenToWorldDistance(event.getY() - lastDragY); - worldCenterX += transX; - worldCenterY += transY; - worldOffsetX += transX; - worldOffsetY += transY; - lastDragX = event.getX(); - lastDragY = event.getY(); - updateCanvas(); - } catch (Exception ez) { - // Catch exception occurring when mouse is out of the canvas - } - } - }); - - canvas.setOnScroll(new EventHandler<ScrollEvent>() { - @Override - public void handle(ScrollEvent event) { - double wdx = screenToWorldDistance(canvas.getWidth() / 2 - event.getX()); - double wdy = screenToWorldDistance(canvas.getHeight() / 2 - event.getY()); - zoom += event.getDeltaY() / event.getMultiplierY() * 10; - if (zoom < 10) - zoom = 10; - - double wdx2 = screenToWorldDistance(canvas.getWidth() / 2 - event.getX()); - double wdy2 = screenToWorldDistance(canvas.getHeight() / 2 - event.getY()); - worldCenterX -= wdx2 - wdx; - worldCenterY -= wdy2 - wdy; - updateCanvas(); - } - }); - - panel.setCenter(canvas); - - //add VuiExplorer - vuiExplorer = new VuiExplorer(this); - panel.setLeft(vuiExplorer); - Button veButton = new Button("VUI explorer"); - veButton.setOnAction(new EventHandler<ActionEvent>() { - @Override - public void handle(ActionEvent event) { - panel.setLeft(vuiExplorer); - } - }); - veButton.setTooltip(new Tooltip("Show the VUI explorer if it was hidden.")); - toolbar.getItems().add(veButton); - - done.release(); - }); - try { - done.acquire(); - } catch (InterruptedException e) { - System.err.println("Failed to make sure that the VUI is correctly initialized."); - e.printStackTrace(); - } - } - - /** - * Convert a distance in the world to its equivalent on the screen - * - * @param d - * the in world distance - * @return the on screen distance - */ - public double worldToScreenDistance(double d) { - return d * getZoomFactor(); - } - - /** - * Convert a distance on the screen to its equivalent in the world - * - * @param d - * the on screen distance - * @return the in world distance - */ - public double screenToWorldDistance(double d) { - return d / getZoomFactor(); - } - - /** - * Convert a X in the world to its equivalent on the screen - * - * @param x - * the X in world - * - * @return the X on screen distance - */ - public double worldToScreenX(double x) { - return (x + getWorldOffsetX()) * getZoomFactor(); - } - - /** - * A value that must be multiplied to scale objects - * - * @return the zoom factor - */ - public double getZoomFactor() { - return zoom / 100; - } - - /** - * Convert a Y in the world to its equivalent on the screen - * - * @param y - * the Y in world - * - * @return the Y on screen distance - */ - public double worldToScreenY(double y) { - return (-y + getWorldOffsetY()) * getZoomFactor(); - } - - /** - * Convert a X on the screen to its equivalent in the world - * - * @param x - * the X on screen - * - * @return the X in the world distance - */ - public double screenToWorldX(double x) { - return x / getZoomFactor() - getWorldOffsetX(); - } - - /** - * Convert a Y on the screen to its equivalent in the world - * - * @param y - * the Y on screen - * - * @return the Y in the world distance - */ - public double screenToWorldY(double y) { - return -y / getZoomFactor() + getWorldOffsetY(); - } - - /** - * Add a drawable to the VUI. - * - * @param d - * the new drawable - */ - public void add(Drawable d) { - d.setVUI(this); - RunLaterHelper.runLater(()-> canvas.getChildren().add(d.getNode())); - drawablesLock.lock(); - drawables.add(d); - drawablesLock.unlock(); - updateCanvas(); - } - - /** - * Remove a drawable from the VUI. - * - * @param d - * the new drawable - */ - public void remove(Drawable d) { - drawablesLock.lock(); - drawables.remove(d); - drawablesLock.unlock(); - RunLaterHelper.runLater(()-> canvas.getChildren().remove(d.getNode())); - updateCanvas(); - } - - /** - * Remove all drawables from the VUI. - */ - public void clear() { - drawablesLock.lock(); - drawables.clear(); - RunLaterHelper.runLater(()->canvas.getChildren().clear()); - drawablesLock.unlock(); - } - - /** - * Refresh the canvas - */ - public void updateCanvas() { - final double w = canvas.getWidth(); - final double h = canvas.getHeight(); - - setWorldOffsetX(worldCenterX + screenToWorldDistance(w / 2)); - setWorldOffsetY(worldCenterY + screenToWorldDistance(h / 2)); - - drawablesLock.lock(); - Collections.sort(drawables, (o1, o2) -> o1.getLayer() - o2.getLayer()); - for (Drawable d : drawables) - RunLaterHelper.runLater(()-> d.onDraw()); - drawablesLock.unlock(); - - RunLaterHelper.runLater(() -> { - statusLabel.setText(String.format("Zoom: %.2f Center: (%.2f,%.2f)", zoom, worldCenterX, worldCenterY)); - }); - - RunLaterHelper.runLater(()-> vuiExplorer.update(true)); - } - - /** - * Get the width of the canvas - * - * @return the canvas width - */ - public double getCanvasWidth() { - return canvas.getWidth(); - } - - /** - * Get the height of the canvas - * - * @return the canvas height - */ - public double getCanvasHeight() { - return canvas.getHeight(); - } - - /** - * Get the value that must be added to the X coordinate of in world object - * - * @return the X offset - */ - public double getWorldOffsetX() { - return worldOffsetX; - } - - /** - * Set the value that must be added to the X coordinate of in world object - * - * @param offsetX - * the X offset - */ - public void setWorldOffsetX(double offsetX) { - this.worldOffsetX = offsetX; - } - - /** - * Get the value that must be added to the Y coordinate of in world object - * - * @return the Y offset - */ - public double getWorldOffsetY() { - return worldOffsetY; - } - - /** - * Set the value that must be added to the Y coordinate of in world object - * - * @param offsetY - * the Y offset - */ - public void setWorldOffsetY(double offsetY) { - this.worldOffsetY = offsetY; - } - - /** - * Create a point and start rendering it - * - * @param dx - * the x coordinate - * @param dy - * the y coordinate - * @return the point object - */ - public DrawablePoint createAndAddPoint(double dx, double dy) { - DrawablePoint drawablePoint = new DrawablePoint(dx, dy); - add(drawablePoint); - return drawablePoint; - } - - /** - * Create a rectangle and start rendering it - * - * @param x - * the x coordinate - * @param y - * the y coordinate - * @param w - * the width - * @param h - * the height - * @return the rectangle object - */ - public DrawableRectangle createAndAddRectangle(double x, double y, double w, double h) { - DrawableRectangle d = new DrawableRectangle(x, y, w, h); - add(d); - return d; - } - - /** - * Set the default configuration of the view - * - * @param zoom - * the initial zoom value - * @param worldCenterX - * the initial X center value - * @param worldCenterY - * the initial Y center value - */ - public void setDefaultView(double zoom, double worldCenterX, double worldCenterY) { - this.zoom = zoom; - this.worldCenterX = worldCenterX; - this.worldCenterY = worldCenterY; - this.defaultZoom = zoom; - this.defaultWorldCenterX = worldCenterX; - this.defaultWorldCenterY = worldCenterY; - } - - /** - * Create an image and start rendering it - * - * @param dx - * the x coordinate - * @param dy - * the y coordinate - * @param filename - * the filename of the image - * @return the created image - */ - public DrawableImage createAndAddImage(double dx, double dy, String filename) { - DrawableImage image = new DrawableImage(dx, dy, filename); - add(image); - return image; - } - - /** - * Create a string and start rendering it - * - * @param dx - * the x coordinate - * @param dy - * the y coordinate - * @param text - * the text to display - * @return the created string - */ - public DrawableString createAndAddString(int dx, int dy, String text) { - DrawableString ds = new DrawableString(dx, dy, text); - add(ds); - return ds; - } - - public Pane getCanvas() { - return canvas; - } - - public BorderPane getPanel() { - return panel; - } - - public List<Drawable> getDrawables() { - return drawables; - } -} +package fr.irit.smac.amak.ui; + +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Semaphore; +import java.util.concurrent.locks.ReentrantLock; + +import fr.irit.smac.amak.tools.RunLaterHelper; +import fr.irit.smac.amak.ui.drawables.Drawable; +import fr.irit.smac.amak.ui.drawables.DrawableImage; +import fr.irit.smac.amak.ui.drawables.DrawablePoint; +import fr.irit.smac.amak.ui.drawables.DrawableRectangle; +import fr.irit.smac.amak.ui.drawables.DrawableString; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.geometry.Insets; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.ToolBar; +import javafx.scene.control.Tooltip; +import javafx.scene.input.MouseEvent; +import javafx.scene.input.ScrollEvent; +import javafx.scene.layout.Background; +import javafx.scene.layout.BackgroundFill; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.CornerRadii; +import javafx.scene.layout.Pane; +import javafx.scene.paint.Color; +import javafx.scene.shape.Rectangle; +import javafx.scene.text.TextAlignment; + +/** + * + * Vectorial UI: This class allows to create dynamic rendering with zoom and + * move capacities + * + * @author of original version (the Swing one) perles + * + */ +public class VUI { + /** + * The toolbar of the VUI. + */ + public ToolBar toolbar; + + /** + * The VUI explorer. + * @see VuiExplorer + */ + private VuiExplorer vuiExplorer; + + /** + * List of objects currently being drawn by the VUI + */ + private List<Drawable> drawables = new LinkedList<>(); + /** + * Lock to avoid concurrent modification on the list {@link #drawables} + */ + private ReentrantLock drawablesLock = new ReentrantLock(); + + /** + * A static map to facilitate access to different instances of VUI + */ + private static Map<String, VUI> instances = new HashMap<>(); + + /** + * The horizontal offset of the drawing zone. Used to allow the user to move the + * view. + */ + private double worldOffsetX; + + /** + * The vertical offset of the drawing zone. Used to allow the user to move the + * view. + */ + private double worldOffsetY; + + /** + * The last horizontal position of the mouse when dragging + */ + protected Double lastDragX; + + /** + * The last vertical position of the mouse when dragging + */ + protected Double lastDragY; + + /** + * The main panel of the VUI + */ + private BorderPane panel; + + /** + * The canvas on which all is drawn + */ + private Pane canvas; + + /** + * Label aiming at showing information about the VUI (zoom and offset) + */ + private Label statusLabel; + + /** + * The default value of the {@link #zoom} + */ + private double defaultZoom = 100; + /** + * The default horizontal position of the view + */ + private double defaultWorldCenterX = 0; + /** + * The default vertical position of the view + */ + private double defaultWorldCenterY = 0; + /** + * The value of the zoom. 100 means 1/1 scale + */ + protected double zoom = defaultZoom; + + /** + * The horizontal position of the view + */ + private double worldCenterX = defaultWorldCenterX; + + /** + * The vertical position of the view + */ + private double worldCenterY = defaultWorldCenterY; + + /** + * Used to be sure that only one thread at the same time create a VUI + */ + private static ReentrantLock instanceLock = new ReentrantLock(); + + /** + * Get the default VUI + * + * @return the default VUI + */ + public static VUI get() { + if(!instances.containsKey("Default")) + MainWindow.addTabbedPanel("Default VUI", get("Default").getPanel()); + return get("Default"); + } + + + + /** + * Create or get a VUI.<br/> + * You have add its panel to the MainWindow yourself. + * + * @param id + * The unique id of the VUI + * @return The VUI with id "id" + */ + public static VUI get(String id) { + instanceLock.lock(); + if (!instances.containsKey(id)) { + VUI value = new VUI(id); + instances.put(id, value); + instanceLock.unlock(); + return value; + } + instanceLock.unlock(); + return instances.get(id); + } + + /** + * Constructor of the VUI. This one is private as it can only be created through + * static method. + * + * @param title + * The title used for the vui + */ + private VUI(String title) { + Semaphore done = new Semaphore(0); + RunLaterHelper.runLater(() -> { + panel = new BorderPane(); + + toolbar = new ToolBar(); + statusLabel = new Label("status"); + statusLabel.setTextAlignment(TextAlignment.LEFT); + toolbar.getItems().add(statusLabel); + panel.setBottom(toolbar); + + Button resetButton = new Button("Reset"); + resetButton.setOnAction(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent event) { + zoom = defaultZoom; + worldCenterX = defaultWorldCenterX; + worldCenterY = defaultWorldCenterY; + updateCanvas(); + } + }); + toolbar.getItems().add(resetButton); + + canvas = new Pane(); + canvas.setBackground(new Background(new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, Insets.EMPTY))); + // clip the canvas (avoid drawing outside of it) + Rectangle clip = new Rectangle(0, 0, 0, 0); + clip.widthProperty().bind(canvas.widthProperty()); + clip.heightProperty().bind(canvas.heightProperty()); + canvas.setClip(clip); + + canvas.setOnMousePressed(new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + lastDragX = event.getX(); + lastDragY = event.getY(); + } + }); + canvas.setOnMouseExited(new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + lastDragX = null; + lastDragY = null; + } + }); + canvas.setOnMouseDragged(new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + try { + double transX = screenToWorldDistance(event.getX() - lastDragX); + double transY = screenToWorldDistance(event.getY() - lastDragY); + worldCenterX += transX; + worldCenterY += transY; + worldOffsetX += transX; + worldOffsetY += transY; + lastDragX = event.getX(); + lastDragY = event.getY(); + updateCanvas(); + } catch (Exception ez) { + // Catch exception occurring when mouse is out of the canvas + } + } + }); + + canvas.setOnScroll(new EventHandler<ScrollEvent>() { + @Override + public void handle(ScrollEvent event) { + double wdx = screenToWorldDistance(canvas.getWidth() / 2 - event.getX()); + double wdy = screenToWorldDistance(canvas.getHeight() / 2 - event.getY()); + zoom += event.getDeltaY() / event.getMultiplierY() * 10; + if (zoom < 10) + zoom = 10; + + double wdx2 = screenToWorldDistance(canvas.getWidth() / 2 - event.getX()); + double wdy2 = screenToWorldDistance(canvas.getHeight() / 2 - event.getY()); + worldCenterX -= wdx2 - wdx; + worldCenterY -= wdy2 - wdy; + updateCanvas(); + } + }); + + panel.setCenter(canvas); + + //add VuiExplorer + vuiExplorer = new VuiExplorer(this); + panel.setLeft(vuiExplorer); + Button veButton = new Button("VUI explorer"); + veButton.setOnAction(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent event) { + panel.setLeft(vuiExplorer); + } + }); + veButton.setTooltip(new Tooltip("Show the VUI explorer if it was hidden.")); + toolbar.getItems().add(veButton); + + done.release(); + }); + try { + done.acquire(); + } catch (InterruptedException e) { + System.err.println("Failed to make sure that the VUI is correctly initialized."); + e.printStackTrace(); + } + } + + /** + * Convert a distance in the world to its equivalent on the screen + * + * @param d + * the in world distance + * @return the on screen distance + */ + public double worldToScreenDistance(double d) { + return d * getZoomFactor(); + } + + /** + * Convert a distance on the screen to its equivalent in the world + * + * @param d + * the on screen distance + * @return the in world distance + */ + public double screenToWorldDistance(double d) { + return d / getZoomFactor(); + } + + /** + * Convert a X in the world to its equivalent on the screen + * + * @param x + * the X in world + * + * @return the X on screen distance + */ + public double worldToScreenX(double x) { + return (x + getWorldOffsetX()) * getZoomFactor(); + } + + /** + * A value that must be multiplied to scale objects + * + * @return the zoom factor + */ + public double getZoomFactor() { + return zoom / 100; + } + + /** + * Convert a Y in the world to its equivalent on the screen + * + * @param y + * the Y in world + * + * @return the Y on screen distance + */ + public double worldToScreenY(double y) { + return (-y + getWorldOffsetY()) * getZoomFactor(); + } + + /** + * Convert a X on the screen to its equivalent in the world + * + * @param x + * the X on screen + * + * @return the X in the world distance + */ + public double screenToWorldX(double x) { + return x / getZoomFactor() - getWorldOffsetX(); + } + + /** + * Convert a Y on the screen to its equivalent in the world + * + * @param y + * the Y on screen + * + * @return the Y in the world distance + */ + public double screenToWorldY(double y) { + return -y / getZoomFactor() + getWorldOffsetY(); + } + + /** + * Add a drawable to the VUI. + * + * @param d + * the new drawable + */ + public void add(Drawable d) { + d.setVUI(this); + RunLaterHelper.runLater(()-> canvas.getChildren().add(d.getNode())); + drawablesLock.lock(); + drawables.add(d); + drawablesLock.unlock(); + updateCanvas(); + } + + /** + * Remove a drawable from the VUI. + * + * @param d + * the new drawable + */ + public void remove(Drawable d) { + drawablesLock.lock(); + drawables.remove(d); + drawablesLock.unlock(); + RunLaterHelper.runLater(()-> canvas.getChildren().remove(d.getNode())); + updateCanvas(); + } + + /** + * Remove all drawables from the VUI. + */ + public void clear() { + drawablesLock.lock(); + drawables.clear(); + RunLaterHelper.runLater(()->canvas.getChildren().clear()); + drawablesLock.unlock(); + } + + /** + * Refresh the canvas + */ + public void updateCanvas() { + final double w = canvas.getWidth(); + final double h = canvas.getHeight(); + + setWorldOffsetX(worldCenterX + screenToWorldDistance(w / 2)); + setWorldOffsetY(worldCenterY + screenToWorldDistance(h / 2)); + + drawablesLock.lock(); + Collections.sort(drawables, (o1, o2) -> o1.getLayer() - o2.getLayer()); + for (Drawable d : drawables) + RunLaterHelper.runLater(()-> d.onDraw()); + drawablesLock.unlock(); + + RunLaterHelper.runLater(() -> { + statusLabel.setText(String.format("Zoom: %.2f Center: (%.2f,%.2f)", zoom, worldCenterX, worldCenterY)); + }); + + RunLaterHelper.runLater(()-> vuiExplorer.update(true)); + } + + /** + * Get the width of the canvas + * + * @return the canvas width + */ + public double getCanvasWidth() { + return canvas.getWidth(); + } + + /** + * Get the height of the canvas + * + * @return the canvas height + */ + public double getCanvasHeight() { + return canvas.getHeight(); + } + + /** + * Get the value that must be added to the X coordinate of in world object + * + * @return the X offset + */ + public double getWorldOffsetX() { + return worldOffsetX; + } + + /** + * Set the value that must be added to the X coordinate of in world object + * + * @param offsetX + * the X offset + */ + public void setWorldOffsetX(double offsetX) { + this.worldOffsetX = offsetX; + } + + /** + * Get the value that must be added to the Y coordinate of in world object + * + * @return the Y offset + */ + public double getWorldOffsetY() { + return worldOffsetY; + } + + /** + * Set the value that must be added to the Y coordinate of in world object + * + * @param offsetY + * the Y offset + */ + public void setWorldOffsetY(double offsetY) { + this.worldOffsetY = offsetY; + } + + /** + * Create a point and start rendering it + * + * @param dx + * the x coordinate + * @param dy + * the y coordinate + * @return the point object + */ + public DrawablePoint createAndAddPoint(double dx, double dy) { + DrawablePoint drawablePoint = new DrawablePoint(dx, dy); + add(drawablePoint); + return drawablePoint; + } + + /** + * Create a rectangle and start rendering it + * + * @param x + * the x coordinate + * @param y + * the y coordinate + * @param w + * the width + * @param h + * the height + * @return the rectangle object + */ + public DrawableRectangle createAndAddRectangle(double x, double y, double w, double h) { + DrawableRectangle d = new DrawableRectangle(x, y, w, h); + add(d); + return d; + } + + /** + * Set the default configuration of the view + * + * @param zoom + * the initial zoom value + * @param worldCenterX + * the initial X center value + * @param worldCenterY + * the initial Y center value + */ + public void setDefaultView(double zoom, double worldCenterX, double worldCenterY) { + this.zoom = zoom; + this.worldCenterX = worldCenterX; + this.worldCenterY = worldCenterY; + this.defaultZoom = zoom; + this.defaultWorldCenterX = worldCenterX; + this.defaultWorldCenterY = worldCenterY; + } + + /** + * Create an image and start rendering it + * + * @param dx + * the x coordinate + * @param dy + * the y coordinate + * @param filename + * the filename of the image + * @return the created image + */ + public DrawableImage createAndAddImage(double dx, double dy, String filename) { + DrawableImage image = new DrawableImage(dx, dy, filename); + add(image); + return image; + } + + /** + * Create a string and start rendering it + * + * @param dx + * the x coordinate + * @param dy + * the y coordinate + * @param text + * the text to display + * @return the created string + */ + public DrawableString createAndAddString(int dx, int dy, String text) { + DrawableString ds = new DrawableString(dx, dy, text); + add(ds); + return ds; + } + + public Pane getCanvas() { + return canvas; + } + + public BorderPane getPanel() { + return panel; + } + + public List<Drawable> getDrawables() { + return drawables; + } +} diff --git a/AMAKFX/src/fr/irit/smac/amak/ui/VUIMulti.java b/AMAKFX/src/fr/irit/smac/amak/ui/VUIMulti.java new file mode 100644 index 0000000000000000000000000000000000000000..eeeb4862787055d93d93e4eb772108c06dab4e44 --- /dev/null +++ b/AMAKFX/src/fr/irit/smac/amak/ui/VUIMulti.java @@ -0,0 +1,546 @@ +package fr.irit.smac.amak.ui; + +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Semaphore; +import java.util.concurrent.locks.ReentrantLock; + +import fr.irit.smac.amak.tools.RunLaterHelper; +import fr.irit.smac.amak.ui.drawables.Drawable; +import fr.irit.smac.amak.ui.drawables.DrawableImage; +import fr.irit.smac.amak.ui.drawables.DrawablePoint; +import fr.irit.smac.amak.ui.drawables.DrawableRectangle; +import fr.irit.smac.amak.ui.drawables.DrawableString; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.geometry.Insets; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.ToolBar; +import javafx.scene.control.Tooltip; +import javafx.scene.input.MouseEvent; +import javafx.scene.input.ScrollEvent; +import javafx.scene.layout.Background; +import javafx.scene.layout.BackgroundFill; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.CornerRadii; +import javafx.scene.layout.Pane; +import javafx.scene.paint.Color; +import javafx.scene.shape.Rectangle; +import javafx.scene.text.TextAlignment; + +/** + * + * Vectorial UI: This class allows to create dynamic rendering with zoom and + * move capacities + * + * @author of original version (the Swing one) perles + * + */ +public class VUIMulti { + + public String title; + + /** + * The toolbar of the VUI. + */ + public ToolBar toolbar; + + /** + * The VUI explorer. + * @see VuiExplorer + */ + private VuiExplorer vuiExplorer; + + /** + * List of objects currently being drawn by the VUI + */ + private List<Drawable> drawables = new LinkedList<>(); + /** + * Lock to avoid concurrent modification on the list {@link #drawables} + */ + private ReentrantLock drawablesLock = new ReentrantLock(); + + /** + * A static map to facilitate access to different instances of VUI + */ + //private static Map<String, VUIMulti> instances = new HashMap<>(); + + /** + * The horizontal offset of the drawing zone. Used to allow the user to move the + * view. + */ + private double worldOffsetX; + + /** + * The vertical offset of the drawing zone. Used to allow the user to move the + * view. + */ + private double worldOffsetY; + + /** + * The last horizontal position of the mouse when dragging + */ + protected Double lastDragX; + + /** + * The last vertical position of the mouse when dragging + */ + protected Double lastDragY; + + /** + * The main panel of the VUI + */ + private BorderPane panel; + + /** + * The canvas on which all is drawn + */ + private Pane canvas; + + /** + * Label aiming at showing information about the VUI (zoom and offset) + */ + private Label statusLabel; + + /** + * The default value of the {@link #zoom} + */ + private double defaultZoom = 100; + /** + * The default horizontal position of the view + */ + private double defaultWorldCenterX = 0; + /** + * The default vertical position of the view + */ + private double defaultWorldCenterY = 0; + /** + * The value of the zoom. 100 means 1/1 scale + */ + protected double zoom = defaultZoom; + + /** + * The horizontal position of the view + */ + private double worldCenterX = defaultWorldCenterX; + + /** + * The vertical position of the view + */ + private double worldCenterY = defaultWorldCenterY; + + + + + + /** + * Constructor of the VUI. This one is private as it can only be created through + * static method. + * + * @param title + * The title used for the vui + */ + public VUIMulti(String titleValue) { + + RunLaterHelper.runLater(() -> { + + this.title = titleValue; + panel = new BorderPane(); + + toolbar = new ToolBar(); + statusLabel = new Label("status"); + statusLabel.setTextAlignment(TextAlignment.LEFT); + toolbar.getItems().add(statusLabel); + panel.setBottom(toolbar); + + Button resetButton = new Button("Reset"); + resetButton.setOnAction(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent event) { + zoom = defaultZoom; + worldCenterX = defaultWorldCenterX; + worldCenterY = defaultWorldCenterY; + updateCanvas(); + } + }); + toolbar.getItems().add(resetButton); + + canvas = new Pane(); + canvas.setBackground(new Background(new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, Insets.EMPTY))); + // clip the canvas (avoid drawing outside of it) + Rectangle clip = new Rectangle(0, 0, 0, 0); + clip.widthProperty().bind(canvas.widthProperty()); + clip.heightProperty().bind(canvas.heightProperty()); + canvas.setClip(clip); + + canvas.setOnMousePressed(new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + lastDragX = event.getX(); + lastDragY = event.getY(); + } + }); + canvas.setOnMouseExited(new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + lastDragX = null; + lastDragY = null; + } + }); + canvas.setOnMouseDragged(new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + try { + double transX = screenToWorldDistance(event.getX() - lastDragX); + double transY = screenToWorldDistance(event.getY() - lastDragY); + worldCenterX += transX; + worldCenterY += transY; + worldOffsetX += transX; + worldOffsetY += transY; + lastDragX = event.getX(); + lastDragY = event.getY(); + updateCanvas(); + } catch (Exception ez) { + // Catch exception occurring when mouse is out of the canvas + } + } + }); + + canvas.setOnScroll(new EventHandler<ScrollEvent>() { + @Override + public void handle(ScrollEvent event) { + double wdx = screenToWorldDistance(canvas.getWidth() / 2 - event.getX()); + double wdy = screenToWorldDistance(canvas.getHeight() / 2 - event.getY()); + zoom += event.getDeltaY() / event.getMultiplierY() * 10; + if (zoom < 10) + zoom = 10; + + double wdx2 = screenToWorldDistance(canvas.getWidth() / 2 - event.getX()); + double wdy2 = screenToWorldDistance(canvas.getHeight() / 2 - event.getY()); + worldCenterX -= wdx2 - wdx; + worldCenterY -= wdy2 - wdy; + updateCanvas(); + } + }); + + panel.setCenter(canvas); + + //add VuiExplorer + vuiExplorer = new VuiExplorer(this); + panel.setLeft(vuiExplorer); + Button veButton = new Button("VUI explorer"); + veButton.setOnAction(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent event) { + panel.setLeft(vuiExplorer); + } + }); + veButton.setTooltip(new Tooltip("Show the VUI explorer if it was hidden.")); + toolbar.getItems().add(veButton); + + + }); + + } + + /** + * Convert a distance in the world to its equivalent on the screen + * + * @param d + * the in world distance + * @return the on screen distance + */ + public double worldToScreenDistance(double d) { + return d * getZoomFactor(); + } + + /** + * Convert a distance on the screen to its equivalent in the world + * + * @param d + * the on screen distance + * @return the in world distance + */ + public double screenToWorldDistance(double d) { + return d / getZoomFactor(); + } + + /** + * Convert a X in the world to its equivalent on the screen + * + * @param x + * the X in world + * + * @return the X on screen distance + */ + public double worldToScreenX(double x) { + return (x + getWorldOffsetX()) * getZoomFactor(); + } + + /** + * A value that must be multiplied to scale objects + * + * @return the zoom factor + */ + public double getZoomFactor() { + return zoom / 100; + } + + /** + * Convert a Y in the world to its equivalent on the screen + * + * @param y + * the Y in world + * + * @return the Y on screen distance + */ + public double worldToScreenY(double y) { + return (-y + getWorldOffsetY()) * getZoomFactor(); + } + + /** + * Convert a X on the screen to its equivalent in the world + * + * @param x + * the X on screen + * + * @return the X in the world distance + */ + public double screenToWorldX(double x) { + return x / getZoomFactor() - getWorldOffsetX(); + } + + /** + * Convert a Y on the screen to its equivalent in the world + * + * @param y + * the Y on screen + * + * @return the Y in the world distance + */ + public double screenToWorldY(double y) { + return -y / getZoomFactor() + getWorldOffsetY(); + } + + /** + * Add a drawable to the VUI. + * + * @param d + * the new drawable + */ + public void add(Drawable d) { + + d.setVUIMulti(this); + RunLaterHelper.runLater(()-> canvas.getChildren().add(d.getNode())); + drawablesLock.lock(); + drawables.add(d); + drawablesLock.unlock(); + updateCanvas(); + } + + /** + * Remove a drawable from the VUI. + * + * @param d + * the new drawable + */ + public void remove(Drawable d) { + drawablesLock.lock(); + drawables.remove(d); + drawablesLock.unlock(); + RunLaterHelper.runLater(()-> canvas.getChildren().remove(d.getNode())); + updateCanvas(); + } + + /** + * Remove all drawables from the VUI. + */ + public void clear() { + drawablesLock.lock(); + drawables.clear(); + RunLaterHelper.runLater(()->canvas.getChildren().clear()); + drawablesLock.unlock(); + } + + /** + * Refresh the canvas + */ + public void updateCanvas() { + final double w = canvas.getWidth(); + final double h = canvas.getHeight(); + + setWorldOffsetX(worldCenterX + screenToWorldDistance(w / 2)); + setWorldOffsetY(worldCenterY + screenToWorldDistance(h / 2)); + + drawablesLock.lock(); + Collections.sort(drawables, (o1, o2) -> o1.getLayer() - o2.getLayer()); + for (Drawable d : drawables) + RunLaterHelper.runLater(()-> d.onDraw()); + drawablesLock.unlock(); + + RunLaterHelper.runLater(() -> { + statusLabel.setText(String.format("Zoom: %.2f Center: (%.2f,%.2f)", zoom, worldCenterX, worldCenterY)); + }); + + RunLaterHelper.runLater(()-> vuiExplorer.update(true)); + } + + /** + * Get the width of the canvas + * + * @return the canvas width + */ + public double getCanvasWidth() { + return canvas.getWidth(); + } + + /** + * Get the height of the canvas + * + * @return the canvas height + */ + public double getCanvasHeight() { + return canvas.getHeight(); + } + + /** + * Get the value that must be added to the X coordinate of in world object + * + * @return the X offset + */ + public double getWorldOffsetX() { + return worldOffsetX; + } + + /** + * Set the value that must be added to the X coordinate of in world object + * + * @param offsetX + * the X offset + */ + public void setWorldOffsetX(double offsetX) { + this.worldOffsetX = offsetX; + } + + /** + * Get the value that must be added to the Y coordinate of in world object + * + * @return the Y offset + */ + public double getWorldOffsetY() { + return worldOffsetY; + } + + /** + * Set the value that must be added to the Y coordinate of in world object + * + * @param offsetY + * the Y offset + */ + public void setWorldOffsetY(double offsetY) { + this.worldOffsetY = offsetY; + } + + /** + * Create a point and start rendering it + * + * @param dx + * the x coordinate + * @param dy + * the y coordinate + * @return the point object + */ + public DrawablePoint createAndAddPoint(double dx, double dy) { + DrawablePoint drawablePoint = new DrawablePoint(dx, dy); + add(drawablePoint); + return drawablePoint; + } + + /** + * Create a rectangle and start rendering it + * + * @param x + * the x coordinate + * @param y + * the y coordinate + * @param w + * the width + * @param h + * the height + * @return the rectangle object + */ + public DrawableRectangle createAndAddRectangle(double x, double y, double w, double h) { + DrawableRectangle d = new DrawableRectangle(x, y, w, h); + add(d); + return d; + } + + /** + * Set the default configuration of the view + * + * @param zoom + * the initial zoom value + * @param worldCenterX + * the initial X center value + * @param worldCenterY + * the initial Y center value + */ + public void setDefaultView(double zoom, double worldCenterX, double worldCenterY) { + this.zoom = zoom; + this.worldCenterX = worldCenterX; + this.worldCenterY = worldCenterY; + this.defaultZoom = zoom; + this.defaultWorldCenterX = worldCenterX; + this.defaultWorldCenterY = worldCenterY; + } + + /** + * Create an image and start rendering it + * + * @param dx + * the x coordinate + * @param dy + * the y coordinate + * @param filename + * the filename of the image + * @return the created image + */ + public DrawableImage createAndAddImage(double dx, double dy, String filename) { + DrawableImage image = new DrawableImage(dx, dy, filename); + add(image); + return image; + } + + /** + * Create a string and start rendering it + * + * @param dx + * the x coordinate + * @param dy + * the y coordinate + * @param text + * the text to display + * @return the created string + */ + public DrawableString createAndAddString(int dx, int dy, String text) { + DrawableString ds = new DrawableString(dx, dy, text); + add(ds); + return ds; + } + + public Pane getCanvas() { + return canvas; + } + + public BorderPane getPanel() { + return panel; + } + + public List<Drawable> getDrawables() { + return drawables; + } +} diff --git a/AMAKFX/src/fr/irit/smac/amak/ui/VuiExplorer.java b/AMAKFX/src/fr/irit/smac/amak/ui/VuiExplorer.java index 957e6a8ae9c4507c27f9832abb99d7023ab19883..dbb94088b1911433c82eccdd7a3c5c2a5170c067 100644 --- a/AMAKFX/src/fr/irit/smac/amak/ui/VuiExplorer.java +++ b/AMAKFX/src/fr/irit/smac/amak/ui/VuiExplorer.java @@ -23,13 +23,14 @@ import javafx.scene.layout.VBox; import javafx.scene.paint.Color; /** - * A piece of GUI allowing to see and look for contexts. + * A piece of GUI allowing to see and look for agents. * @author Hugo * */ public class VuiExplorer extends ScrollPane { - private VUI vui; + private VUI vui = null; + private VUIMulti vuiMulti = null; private VBox vbox; private TitledPane contextsPane; @@ -102,6 +103,74 @@ public class VuiExplorer extends ScrollPane { RunLaterHelper.runLater(()->vui.getPanel().setLeft(this)); } + + + public VuiExplorer(VUIMulti vuiMlt) { + this.vuiMulti = vuiMlt; + + this.setMaxWidth(Double.MAX_VALUE); + this.setMaxHeight(Double.MAX_VALUE); + + vbox = new VBox(); + vbox.setFillWidth(true); + this.setContent(vbox); + + // refresh, close, and collapseAll button + HBox hboxButtons = new HBox(); + Button refresh = new Button("Refresh"); + refresh.setOnAction(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent event) { + update(); + } + }); + Button close = new Button("Close"); + close.setOnAction(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent event) { + vuiMulti.getPanel().setLeft(null); + } + }); + Button collapseAll = new Button("Collapse all"); + collapseAll.setOnAction(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent event) { + collapseAll(); + } + }); + hboxButtons.getChildren().addAll(refresh, close, collapseAll); + + // check box + autoRefresh = new CheckBox("Auto refresh"); + autoRefresh.setTooltip(new Tooltip("Try to automatically refresh the VUI explorer when the VUI is updated.")); + + // search bar + search = new TextField(); + search.setPromptText("regular expression"); + // update list on change + search.textProperty().addListener(new ChangeListener<String>() { + @Override + public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) { + search.setStyle(null); + try { + update(); + } catch (PatternSyntaxException ex) { + search.setStyle("-fx-border-color: red;"); + } + } + }); + + cpVBox = new VBox(); + contextsPane = new TitledPane("Drawables", cpVBox); + + vbox.getChildren().addAll(hboxButtons, autoRefresh, search, contextsPane); + update(); + + // Add to vui + RunLaterHelper.runLater(()->vuiMulti.getPanel().setLeft(this)); + + } + public void update(boolean auto) { if(auto && autoRefresh.isSelected()) { @@ -113,7 +182,13 @@ public class VuiExplorer extends ScrollPane { * Update the list of context */ public void update() { - List<Drawable> drawableList = vui.getDrawables(); + List<Drawable> drawableList = null; + if(vui != null) { + drawableList = vui.getDrawables(); + } + if(vuiMulti != null) { + drawableList = vuiMulti.getDrawables(); + } // crude color sort drawableList.sort(new Comparator<Drawable>() { @Override @@ -142,7 +217,13 @@ public class VuiExplorer extends ScrollPane { } private void collapseAll() { - List<Drawable> drawableList = vui.getDrawables(); + List<Drawable> drawableList = null; + if(vui != null) { + drawableList = vui.getDrawables(); + } + if(vuiMulti != null) { + drawableList = vuiMulti.getDrawables(); + } for(Drawable d : drawableList) { if(d.showInExplorer && d.isVisible()) { Drawable mini = d.getLinkedDrawable("mini"); diff --git a/AMAKFX/src/fr/irit/smac/amak/ui/drawables/Drawable.java b/AMAKFX/src/fr/irit/smac/amak/ui/drawables/Drawable.java index ec5ce1fe94b59134da30c00edbd7368c6c8470f3..377377c5904d07114052daeb12d959a6664a725d 100644 --- a/AMAKFX/src/fr/irit/smac/amak/ui/drawables/Drawable.java +++ b/AMAKFX/src/fr/irit/smac/amak/ui/drawables/Drawable.java @@ -1,605 +1,618 @@ -package fr.irit.smac.amak.ui.drawables; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -import fr.irit.smac.amak.tools.RunLaterHelper; -import fr.irit.smac.amak.ui.VUI; -import javafx.event.Event; -import javafx.event.EventHandler; -import javafx.scene.Node; -import javafx.scene.input.MouseEvent; -import javafx.scene.paint.Color; - -/** - * A drawable is an object that can be drawn by the {@link VUI} system - * - * @author of original version (the Swing one) Alexandre Perles - * - */ -public abstract class Drawable { - - /** - * If this drawable should be shown in the vui explorer. - */ - public boolean showInExplorer = true; - - /** - * Default style applied to drawable node. - */ - protected static String defaultStyle = "-fx-stroke: black; -fx-stroke-width: 1;"; - - /** - * Linked drawables will receive the same event when using dispatchEvent. - */ - private HashMap<String, Drawable> linkedDrawable = new HashMap<String, Drawable>(); - - /** - * The horizontal position of the object - */ - protected double x; - /** - * The vertical position of the object - */ - private double y; - /** - * The width of the object - */ - private double width; - - /** - * The real height - */ - protected double height; - - /** - * Does only the border must be displayed ? - */ - protected boolean strokeMode = false; - - /** - * The color of the object - */ - protected Color color = Color.BLACK; - - /** - * The VUI on which the object is drawn - */ - protected VUI vui; - - /** - * The order of drawing. An higher layer is drawn on top of the other. - */ - protected int layer = 0; - - /** - * The angle of rotation of the object - */ - private double angle; - - /** - * A fixed object doesn't move with the view. It can be used for HUD - */ - private boolean fixed = false; - - /** - * Must the object be drawn ? - */ - private boolean visible = true; - - /** - * Is the drawable expanded ? - * @see Drawable#onMouseClick(MouseEvent) - * @see Drawable#expand() - * @see Drawable#collapse() - */ - private boolean expanded = false; - - /** - * If relevant, the name of the drawable, usually it's the name - * of the agent represented by this drawable. - */ - private String name; - - /** - * If relevant, additional info on the drawable, usually it's the - * state of the agent represented by this drawable. - */ - private String info; - - /** - * Constructor of the object - * - * @param vui - * the VUI on which the object must be drawn - * @param dx - * the x real position - * @param dy - * the y real position - * @param width - * the real width - * @param height - * the real height - */ - protected Drawable(double dx, double dy, double width, double height) { - move(dx, dy); - setWidth(width); - setHeight(height); - } - - /** - * If you wish to use some default settings for your drawable.<br/> - * Must be called AFTER the node for your drawable has been created. - */ - protected void defaultInit() { - getNode().setStyle(defaultStyle); - - getNode().addEventHandler(MouseEvent.ANY, new EventHandler<MouseEvent>() { - @Override - public void handle(MouseEvent event) { - dispatchEvent(event); - } - }); - } - - /** - * Compute the width as it must be displayed on screen. Given the zoom factor, - * the width displayed can be different than the real width. - * - * @return the width - */ - public double getRenderedWidth() { - if (isFixed()) - return width; - else - return vui.worldToScreenDistance(width); - } - - /** - * Set the real width of the object - * - * @param width - * The new width - */ - public void setWidth(double width) { - this.width = width; - } - - /** - * Compute the height as it must be displayed on screen. Given the zoom factor, - * the height displayed can be different than the real height. - * - * @return the width - */ - public double getRenderedHeight() { - if (isFixed()) - return height; - else - return vui.worldToScreenDistance(height); - } - - /** - * Set the real height of the object - * - * @param height - * The new height - */ - public void setHeight(double height) { - this.height = height; - } - - /** - * Get the real width - * - * @return the real width - */ - public double getWidth() { - return width; - } - - /** - * Get the real height - * - * @return the real height - */ - public double getHeight() { - return height; - } - - - - /** - * Getter for the fixed attribute - * - * @return if the obejct is fixed - */ - public boolean isFixed() { - return fixed; - } - - /** - * Getter for the angle attribute - * - * @return the angle - */ - public double getAngle() { - return angle; - } - - /** - * Getter for the layer attribute - * - * @return the layer - */ - public int getLayer() { - return layer; - } - - /** - * Set the layer and update - * - * @param layer - * the new layer - * @return the object for chained methods - */ - public Drawable setLayer(int layer) { - this.layer = layer; - return this; - } - - /** - * Set the new angle - * - * @param angle2 - * the new angle - * @return the object for chained methods - */ - public Drawable setAngle(double angle2) { - this.angle = angle2; - return this; - } - - /** - * Draw the object if visible and if on screen - * - */ - public void onDraw() { - if (isVisible()) { - _onDraw(); - } - } - - /** - * Method that must be overrided to draw - */ - public abstract void _onDraw(); - - /** - * Set the associated VUI - * - * @param vectorialUI - */ - public void setVUI(VUI vectorialUI) { - vui = vectorialUI; - } - - /** - * Get the top y coordinate - * - * @return the top y coordinate - */ - public double top() { - if (isFixed()) - return y - height / 2; - else - return vui.worldToScreenY(y - height / 2); - } - - /** - * Get the left x coordinate - * - * @return the left x coordinate - */ - public double left() { - if (isFixed()) - return x - width / 2; - else - return vui.worldToScreenX(x - width / 2); - } - - /** - * Get the bottom y coordinate - * - * @return the bottom y coordinate - */ - public double bottom() { - if (isFixed()) - return y + height / 2; - else - return vui.worldToScreenY(y + height / 2); - } - - /** - * Get the right x coordinate - * - * @return the right x coordinate - */ - public double right() { - if (isFixed()) - return x + width / 2; - else - return vui.worldToScreenX(x + width / 2); - } - - /** - * Only draw the border of the object - * - * @return the object for chained methods - */ - public Drawable setStrokeOnly() { - strokeMode = true; - return this; - } - - /** - * - * @param color - * @return the object for chained methods - */ - public Drawable setColor(Color color) { - if (color == this.color) - return this; - this.color = color; - return this; - } - - /** - * The color of the drawable. - * @return - */ - public Color getColor() { - return color; - } - - /** - * - * @param dx - * @param dy - * @return the object for chained methods - */ - public Drawable move(double dx, double dy) { - if (x == dx && y == dy) - return this; - this.x = dx; - this.y = dy; - return this; - } - - /** - * - * @return the object for chained methods - */ - public Drawable setFixed() { - this.fixed = true; - return this; - } - - /** - * - * @return the object for chained methods - */ - public Drawable show() { - return this.setVisible(true); - } - - protected abstract void _hide(); - - /** - * - * @return - */ - public Drawable hide() { - _hide(); - return this.setVisible(false); - } - - /** - * - * @return - */ - public boolean isVisible() { - return visible; - } - - public abstract void _show(); - - /** - * - * @param visible - * @return the object for chained methods - */ - public Drawable setVisible(boolean visible) { - this.visible = visible; - if (visible) - _show(); - else - _hide(); - return this; - } - - /** - * The graphical element that is displayed - * @return - */ - public abstract Node getNode(); - - /** - * Remove the drawable from its VUI - */ - public void delete() { - vui.remove(this); - } - - /** - * Get the linked drawable or null if it does not exist. - * @param name name of the linked drawable - * @return the linked drawable or null - */ - public Drawable getLinkedDrawable(String name) { - Drawable ret = null; - if(linkedDrawable.containsKey(name)) { - ret = linkedDrawable.get(name); - } - return ret; - } - - /** - * Add a drawable to the list of linked drawables.<br/> - * The relation is not symmetrical. - * @param name - * @param drawable - */ - public void addLinkedDrawable(String name, Drawable drawable) { - linkedDrawable.put(name, drawable); - } - - /** - * Return the list of linked drawables. <br/> - * Linked drawables will receive the same event when using dispatchEvent. - */ - public List<Drawable> getLinkedDrawables(){ - return new ArrayList<Drawable>(linkedDrawable.values()); - } - - /** - * Used by dispatchEvent. Override if you want to register more event with the dispatchEvent - * @param event - */ - protected void onEvent(Event event) { - switch (event.getEventType().getName()) { - case "MOUSE_CLICKED": - onMouseClick((MouseEvent)event); - break; - case "MOUSE_ENTERED": - onMouseEntered((MouseEvent)event); - break; - case "MOUSE_EXITED": - onMouseExited((MouseEvent)event); - break; - default: - break; - } - } - - /** - * Called when onEvent receive a MOUSE_EXITED event. - * @param event - */ - protected void onMouseExited(MouseEvent event) { - getNode().setStyle(defaultStyle); - } - - /** - * Called when onEvent receive a MOUSE_ENTERED event. - * @param event - */ - protected void onMouseEntered(MouseEvent event) { - getNode().setStyle("-fx-stroke: black; -fx-stroke-width: 3;"); - } - - /** - * Called when onEvent receive a MOUSE_CLICKED event. - * @param event - */ - protected void onMouseClick(MouseEvent event) { - if(expanded) { - collapse(); - } else { - expand(); - } - } - - /** - * Dispatch an event to all linked drawable, and this drawable. - * @param event - */ - public void dispatchEvent(Event event) { - for(Drawable d : getLinkedDrawables()) { - d.onEvent(event); - } - onEvent(event); - } - - /** - * If this drawable should be shown in the vui explorer. - */ - public Drawable setShowInExplorer(boolean showInExplorer) { - this.showInExplorer = showInExplorer; - return this; - } - - /** - * If relevant, the name of the drawable, usually it's the name - * of the agent represented by this drawable. - */ - public String getName() { - return name == null ? toString() : name; - } - - /** - * If relevant, additional info on the drawable, usually it's the - * state of the agent represented by this drawable. - */ - public String getInfo() { - return info == null ? toString() : info; - } - - /** - * If relevant, the name of the drawable, usually it's the name - * of the agent represented by this drawable. - */ - public Drawable setName(String name) { - this.name = name; - return this; - } - - /** - * If relevant, additional info on the drawable, usually it's the - * state of the agent represented by this drawable. - */ - public Drawable setInfo(String info) { - this.info = info; - return this; - } - - /** - * Action performed if drawable is clicked while collapsed.<br/> - * By default do nothing - * @see Drawable#collapse() - */ - public void expand() { - expanded = true; - } - - /** - * Action performed if drawable is clicked while expanded. - * @see Drawable#expand() - */ - public void collapse() { - expanded = false; - } - - public boolean isExpanded() { - return expanded; - } - - /** - * Set the drawable on top of all others - * @return - */ - public Drawable toFront() { - RunLaterHelper.runLater(()-> getNode().toFront()); - return this; - } -} +package fr.irit.smac.amak.ui.drawables; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import fr.irit.smac.amak.tools.RunLaterHelper; +import fr.irit.smac.amak.ui.VUI; +import fr.irit.smac.amak.ui.VUIMulti; +import javafx.event.Event; +import javafx.event.EventHandler; +import javafx.scene.Node; +import javafx.scene.input.MouseEvent; +import javafx.scene.paint.Color; + +/** + * A drawable is an object that can be drawn by the {@link VUI} system + * + * @author of original version (the Swing one) Alexandre Perles + * + */ +public abstract class Drawable { + + /** + * If this drawable should be shown in the vui explorer. + */ + public boolean showInExplorer = true; + + /** + * Default style applied to drawable node. + */ + protected static String defaultStyle = "-fx-stroke: black; -fx-stroke-width: 1;"; + + /** + * Linked drawables will receive the same event when using dispatchEvent. + */ + private HashMap<String, Drawable> linkedDrawable = new HashMap<String, Drawable>(); + + /** + * The horizontal position of the object + */ + protected double x; + /** + * The vertical position of the object + */ + private double y; + /** + * The width of the object + */ + private double width; + + /** + * The real height + */ + protected double height; + + /** + * Does only the border must be displayed ? + */ + protected boolean strokeMode = false; + + /** + * The color of the object + */ + protected Color color = Color.BLACK; + + /** + * The VUI on which the object is drawn + */ + protected VUI vui; + + protected VUIMulti vuiMulti; + + /** + * The order of drawing. An higher layer is drawn on top of the other. + */ + protected int layer = 0; + + /** + * The angle of rotation of the object + */ + private double angle; + + /** + * A fixed object doesn't move with the view. It can be used for HUD + */ + private boolean fixed = false; + + /** + * Must the object be drawn ? + */ + private boolean visible = true; + + /** + * Is the drawable expanded ? + * @see Drawable#onMouseClick(MouseEvent) + * @see Drawable#expand() + * @see Drawable#collapse() + */ + private boolean expanded = false; + + /** + * If relevant, the name of the drawable, usually it's the name + * of the agent represented by this drawable. + */ + private String name; + + /** + * If relevant, additional info on the drawable, usually it's the + * state of the agent represented by this drawable. + */ + private String info; + + /** + * Constructor of the object + * + * @param vui + * the VUI on which the object must be drawn + * @param dx + * the x real position + * @param dy + * the y real position + * @param width + * the real width + * @param height + * the real height + */ + protected Drawable(double dx, double dy, double width, double height) { + move(dx, dy); + setWidth(width); + setHeight(height); + } + + /** + * If you wish to use some default settings for your drawable.<br/> + * Must be called AFTER the node for your drawable has been created. + */ + protected void defaultInit() { + getNode().setStyle(defaultStyle); + + getNode().addEventHandler(MouseEvent.ANY, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + dispatchEvent(event); + } + }); + } + + /** + * Compute the width as it must be displayed on screen. Given the zoom factor, + * the width displayed can be different than the real width. + * + * @return the width + */ + public double getRenderedWidth() { + if (isFixed()) + return width; + else + return vui != null ? vui.worldToScreenDistance(width) : vuiMulti.worldToScreenDistance(width); + + } + + /** + * Set the real width of the object + * + * @param width + * The new width + */ + public void setWidth(double width) { + this.width = width; + } + + /** + * Compute the height as it must be displayed on screen. Given the zoom factor, + * the height displayed can be different than the real height. + * + * @return the width + */ + public double getRenderedHeight() { + if (isFixed()) + return height; + else + return vui != null ? vui.worldToScreenDistance(height) : vuiMulti.worldToScreenDistance(height); + } + + /** + * Set the real height of the object + * + * @param height + * The new height + */ + public void setHeight(double height) { + this.height = height; + } + + /** + * Get the real width + * + * @return the real width + */ + public double getWidth() { + return width; + } + + /** + * Get the real height + * + * @return the real height + */ + public double getHeight() { + return height; + } + + + + /** + * Getter for the fixed attribute + * + * @return if the obejct is fixed + */ + public boolean isFixed() { + return fixed; + } + + /** + * Getter for the angle attribute + * + * @return the angle + */ + public double getAngle() { + return angle; + } + + /** + * Getter for the layer attribute + * + * @return the layer + */ + public int getLayer() { + return layer; + } + + /** + * Set the layer and update + * + * @param layer + * the new layer + * @return the object for chained methods + */ + public Drawable setLayer(int layer) { + this.layer = layer; + return this; + } + + /** + * Set the new angle + * + * @param angle2 + * the new angle + * @return the object for chained methods + */ + public Drawable setAngle(double angle2) { + this.angle = angle2; + return this; + } + + /** + * Draw the object if visible and if on screen + * + */ + public void onDraw() { + if (isVisible()) { + _onDraw(); + } + } + + /** + * Method that must be overrided to draw + */ + public abstract void _onDraw(); + + /** + * Set the associated VUI + * + * @param vectorialUI + */ + public void setVUI(VUI vectorialUI) { + vui = vectorialUI; + } + + public void setVUIMulti(VUIMulti vectorialUI) { + vuiMulti = vectorialUI; + } + + /** + * Get the top y coordinate + * + * @return the top y coordinate + */ + public double top() { + if (isFixed()) + return y - height / 2; + else + return vui != null ? vui.worldToScreenY(y - height / 2) : vuiMulti.worldToScreenY(y - height / 2); + } + + /** + * Get the left x coordinate + * + * @return the left x coordinate + */ + public double left() { + if (isFixed()) + return x - width / 2; + else + return vui != null ? vui.worldToScreenX(x - width / 2) : vuiMulti.worldToScreenX(x - width / 2); + + } + + /** + * Get the bottom y coordinate + * + * @return the bottom y coordinate + */ + public double bottom() { + if (isFixed()) + return y + height / 2; + else + return vui != null ? vui.worldToScreenY(y + height / 2) : vuiMulti.worldToScreenY(y + height / 2); + } + + /** + * Get the right x coordinate + * + * @return the right x coordinate + */ + public double right() { + if (isFixed()) + return x + width / 2; + else + return vui != null ? vui.worldToScreenX(x + width / 2) : vuiMulti.worldToScreenX(x + width / 2); + } + + /** + * Only draw the border of the object + * + * @return the object for chained methods + */ + public Drawable setStrokeOnly() { + strokeMode = true; + return this; + } + + /** + * + * @param color + * @return the object for chained methods + */ + public Drawable setColor(Color color) { + if (color == this.color) + return this; + this.color = color; + return this; + } + + /** + * The color of the drawable. + * @return + */ + public Color getColor() { + return color; + } + + /** + * + * @param dx + * @param dy + * @return the object for chained methods + */ + public Drawable move(double dx, double dy) { + if (x == dx && y == dy) + return this; + this.x = dx; + this.y = dy; + return this; + } + + /** + * + * @return the object for chained methods + */ + public Drawable setFixed() { + this.fixed = true; + return this; + } + + /** + * + * @return the object for chained methods + */ + public Drawable show() { + return this.setVisible(true); + } + + protected abstract void _hide(); + + /** + * + * @return + */ + public Drawable hide() { + _hide(); + return this.setVisible(false); + } + + /** + * + * @return + */ + public boolean isVisible() { + return visible; + } + + public abstract void _show(); + + /** + * + * @param visible + * @return the object for chained methods + */ + public Drawable setVisible(boolean visible) { + this.visible = visible; + if (visible) + _show(); + else + _hide(); + return this; + } + + /** + * The graphical element that is displayed + * @return + */ + public abstract Node getNode(); + + /** + * Remove the drawable from its VUI + */ + public void delete() { + if(vui != null) + vui.remove(this); + + if(vuiMulti != null) + vuiMulti.remove(this); + } + + /** + * Get the linked drawable or null if it does not exist. + * @param name name of the linked drawable + * @return the linked drawable or null + */ + public Drawable getLinkedDrawable(String name) { + Drawable ret = null; + if(linkedDrawable.containsKey(name)) { + ret = linkedDrawable.get(name); + } + return ret; + } + + /** + * Add a drawable to the list of linked drawables.<br/> + * The relation is not symmetrical. + * @param name + * @param drawable + */ + public void addLinkedDrawable(String name, Drawable drawable) { + linkedDrawable.put(name, drawable); + } + + /** + * Return the list of linked drawables. <br/> + * Linked drawables will receive the same event when using dispatchEvent. + */ + public List<Drawable> getLinkedDrawables(){ + return new ArrayList<Drawable>(linkedDrawable.values()); + } + + /** + * Used by dispatchEvent. Override if you want to register more event with the dispatchEvent + * @param event + */ + protected void onEvent(Event event) { + switch (event.getEventType().getName()) { + case "MOUSE_CLICKED": + onMouseClick((MouseEvent)event); + break; + case "MOUSE_ENTERED": + onMouseEntered((MouseEvent)event); + break; + case "MOUSE_EXITED": + onMouseExited((MouseEvent)event); + break; + default: + break; + } + } + + /** + * Called when onEvent receive a MOUSE_EXITED event. + * @param event + */ + protected void onMouseExited(MouseEvent event) { + getNode().setStyle(defaultStyle); + } + + /** + * Called when onEvent receive a MOUSE_ENTERED event. + * @param event + */ + protected void onMouseEntered(MouseEvent event) { + getNode().setStyle("-fx-stroke: black; -fx-stroke-width: 3;"); + } + + /** + * Called when onEvent receive a MOUSE_CLICKED event. + * @param event + */ + protected void onMouseClick(MouseEvent event) { + if(expanded) { + collapse(); + } else { + expand(); + } + } + + /** + * Dispatch an event to all linked drawable, and this drawable. + * @param event + */ + public void dispatchEvent(Event event) { + for(Drawable d : getLinkedDrawables()) { + d.onEvent(event); + } + onEvent(event); + } + + /** + * If this drawable should be shown in the vui explorer. + */ + public Drawable setShowInExplorer(boolean showInExplorer) { + this.showInExplorer = showInExplorer; + return this; + } + + /** + * If relevant, the name of the drawable, usually it's the name + * of the agent represented by this drawable. + */ + public String getName() { + return name == null ? toString() : name; + } + + /** + * If relevant, additional info on the drawable, usually it's the + * state of the agent represented by this drawable. + */ + public String getInfo() { + return info == null ? toString() : info; + } + + /** + * If relevant, the name of the drawable, usually it's the name + * of the agent represented by this drawable. + */ + public Drawable setName(String name) { + this.name = name; + return this; + } + + /** + * If relevant, additional info on the drawable, usually it's the + * state of the agent represented by this drawable. + */ + public Drawable setInfo(String info) { + this.info = info; + return this; + } + + /** + * Action performed if drawable is clicked while collapsed.<br/> + * By default do nothing + * @see Drawable#collapse() + */ + public void expand() { + expanded = true; + } + + /** + * Action performed if drawable is clicked while expanded. + * @see Drawable#expand() + */ + public void collapse() { + expanded = false; + } + + public boolean isExpanded() { + return expanded; + } + + /** + * Set the drawable on top of all others + * @return + */ + public Drawable toFront() { + RunLaterHelper.runLater(()-> getNode().toFront()); + return this; + } +} diff --git a/AMAKFX/src/fr/irit/smac/amak/ui/drawables/DrawablePoint.java b/AMAKFX/src/fr/irit/smac/amak/ui/drawables/DrawablePoint.java index f17758a89e8b780e74d300884c1e325ea36f70ae..13145dff91cdde22924968cb7d21fe59d8953aae 100644 --- a/AMAKFX/src/fr/irit/smac/amak/ui/drawables/DrawablePoint.java +++ b/AMAKFX/src/fr/irit/smac/amak/ui/drawables/DrawablePoint.java @@ -1,65 +1,65 @@ -package fr.irit.smac.amak.ui.drawables; - -import javafx.event.EventHandler; -import javafx.scene.Node; -import javafx.scene.input.MouseEvent; -import javafx.scene.shape.SVGPath; - -/** - * Drawable to point things on the VUI, use a '+' icon as graphical representation. - * @author Hugo - * - */ -public class DrawablePoint extends Drawable { - - private SVGPath svg = new SVGPath(); - - public DrawablePoint(double dx, double dy) { - super(dx, dy, 0.5, 0.5); - svg.setContent("M24 10h-10v-10h-4v10h-10v4h10v10h4v-10h10z"); - getNode().addEventHandler(MouseEvent.ANY, new EventHandler<MouseEvent>() { - @Override - public void handle(MouseEvent event) { - dispatchEvent(event); - } - }); - } - - @Override - public void _onDraw() { - svg.setFill(color); - svg.setScaleX(getRenderedWidth()); - svg.setScaleY(getRenderedHeight()); - // the render has an offset, 10 look like a good value - svg.setTranslateX(left()-10); - svg.setTranslateY(top()-10); - } - - @Override - protected void _hide() { - svg.setVisible(false); - } - - @Override - public void _show() { - svg.setVisible(true); - } - - @Override - public Node getNode() { - return svg; - } - - @Override - protected void onMouseExited(MouseEvent event) { - svg.setScaleX(getRenderedWidth()); - svg.setScaleY(getRenderedHeight()); - } - - @Override - protected void onMouseEntered(MouseEvent event) { - svg.setScaleX(getRenderedWidth()*1.5); - svg.setScaleY(getRenderedHeight()*1.5); - } - -} +package fr.irit.smac.amak.ui.drawables; + +import javafx.event.EventHandler; +import javafx.scene.Node; +import javafx.scene.input.MouseEvent; +import javafx.scene.shape.SVGPath; + +/** + * Drawable to point things on the VUI, use a '+' icon as graphical representation. + * @author Hugo + * + */ +public class DrawablePoint extends Drawable { + + private SVGPath svg = new SVGPath(); + + public DrawablePoint(double dx, double dy) { + super(dx, dy, 0.1, 0.1); + svg.setContent("M24 10h-10v-10h-4v10h-10v4h10v10h4v-10h10z"); + getNode().addEventHandler(MouseEvent.ANY, new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + dispatchEvent(event); + } + }); + } + + @Override + public void _onDraw() { + svg.setFill(color); + svg.setScaleX(getRenderedWidth()); + svg.setScaleY(getRenderedHeight()); + // the render has an offset, 10 look like a good value + svg.setTranslateX(left()-10); + svg.setTranslateY(top()-10); + } + + @Override + protected void _hide() { + svg.setVisible(false); + } + + @Override + public void _show() { + svg.setVisible(true); + } + + @Override + public Node getNode() { + return svg; + } + + @Override + protected void onMouseExited(MouseEvent event) { + svg.setScaleX(getRenderedWidth()); + svg.setScaleY(getRenderedHeight()); + } + + @Override + protected void onMouseEntered(MouseEvent event) { + svg.setScaleX(getRenderedWidth()*1.5); + svg.setScaleY(getRenderedHeight()*1.5); + } + +} diff --git a/AMOEBAonAMAK/README.md b/AMOEBAonAMAK/README.md deleted file mode 100644 index daac3b82884bc2879d8684289a57e32b486695ba..0000000000000000000000000000000000000000 --- a/AMOEBAonAMAK/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# AMOEBA on AMAK -An AMOEBA3 port on AMAK. - -## Quick start -```Java -// Create a world and a studied system for your amoeba -World world = new World(); -StudiedSystem studiedSystem = new F_XY_System(50.0); -AMOEBA amoeba = new AMOEBA(world, studiedSystem); -// Create a backup system for the amoeba -IBackupSystem backupSystem = new BackupSystem(amoeba); -// Load a configuration matching the studied system -File file = new File("resources\\twoDimensionsLauncher.xml"); -backupSystem.loadXML(file); -// The amoeba is ready to be used -``` -You can find a more complete exemple at `experiment.Main.java`. \ No newline at end of file diff --git a/AMOEBAonAMAK/src/agents/AmoebaAgent.java b/AMOEBAonAMAK/src/agents/AmoebaAgent.java index 45a2c6c1ba8e1985d2d4a11c0c2075cfeff62fb5..8ae90bc81b63ebea93836dfe2a4d0c0d07bab4fc 100644 --- a/AMOEBAonAMAK/src/agents/AmoebaAgent.java +++ b/AMOEBAonAMAK/src/agents/AmoebaAgent.java @@ -1,109 +1,110 @@ -package agents; - -import agents.percept.Percept; -import fr.irit.smac.amak.Agent; -import fr.irit.smac.amak.tools.Loggable; -import gui.RenderStrategy; -import kernel.AMOEBA; -import kernel.World; - -/** - * The base class for all AMOEBA agents - */ -public abstract class AmoebaAgent extends Agent<AMOEBA, World> implements Loggable { - // Attributes - protected String name; - private boolean dying; - - protected RenderStrategy renderStrategy; - - /** - * Instantiate a new agent attached to an amoeba - * @param the amoeba - */ - public AmoebaAgent(AMOEBA amas, Object... params) { - super(amas, params); - this.dying = false; - } - - @Override - protected void onReady() { - super.onReady(); - logger().debug("CYCLE "+getAmas().getCycle(), "Agent %s ready.", toString()); - } - - @Override - protected void onDecide() { - } - - @Override - protected void onRenderingInitialization() { - if(renderStrategy != null) { - renderStrategy.initialize(); - } - } - - @Override - public void onUpdateRender() { - amas.getEnvironment().incrementNbActivatedAgent(); - if(renderStrategy != null && !isDying()) { - if (amas.isRenderUpdate()) { - renderStrategy.render(); - } - } - } - - /** - * Set the name of the agent. Useful for visualization, and essential for {@link Percept}. - * @param name - */ - public void setName(String name) { - this.name = name; - } - - @Override - public void destroy() { - dying = true; - if(renderStrategy != null) { - renderStrategy.delete(); - } - super.destroy(); - logger().debug("CYCLE "+getAmas().getCycle(), "Agent %s destroyed.", toString()); - } - - /** - * Get the name of the agent. Useful for visualization, and essential for {@link Percept}. - * @param name - */ - public String getName() { - return name; - } - - /** - * Tell if the agent is dying. A dying agent no longer perform any useful action, but is not yet removed from its system. - * @return - */ - public boolean isDying() { - return dying; - } - - /** - * Set the render strategy of an agent.<br/> - * {@link RenderStrategy#delete()} the old one, and {@link RenderStrategy#initialize()} the new one. - * @param renderStrategy - * @see RenderStrategy - */ - public void setRenderStrategy(RenderStrategy renderStrategy) { - if(this.renderStrategy != null) this.renderStrategy.delete(); - this.renderStrategy = renderStrategy; - if(this.renderStrategy != null) this.renderStrategy.initialize(); - } - - /** - * Get the render strategy of an agent. - * @return - */ - public RenderStrategy getRenderStrategy() { - return renderStrategy; - } -} +package agents; + +import agents.percept.Percept; +import fr.irit.smac.amak.Agent; +import fr.irit.smac.amak.tools.Loggable; +import gui.RenderStrategy; +import kernel.AMOEBA; +import kernel.World; + +/** + * The base class for all AMOEBA agents + */ +public abstract class AmoebaAgent extends Agent<AMOEBA, World> implements Loggable { + // Attributes + protected String name; + private boolean dying; + + protected RenderStrategy renderStrategy; + + /** + * Instantiate a new agent attached to an amoeba + * @param the amoeba + */ + public AmoebaAgent(AMOEBA amas, Object... params) { + super(amas, params); + this.dying = false; + } + + @Override + protected void onReady() { + super.onReady(); + logger().debug("CYCLE "+getAmas().getCycle(), "Agent %s ready.", toString()); + } + + @Override + protected void onDecide() { + } + + @Override + protected void onRenderingInitialization() { + if(renderStrategy != null) { + renderStrategy.initialize(getAmas().getVUIMulti()); + + } + } + + @Override + public void onUpdateRender() { + amas.getEnvironment().incrementNbActivatedAgent(); + if(renderStrategy != null && !isDying()) { + if (amas.isRenderUpdate()) { + renderStrategy.render(); + } + } + } + + /** + * Set the name of the agent. Useful for visualization, and essential for {@link Percept}. + * @param name + */ + public void setName(String name) { + this.name = name; + } + + @Override + public void destroy() { + dying = true; + if(renderStrategy != null) { + renderStrategy.delete(); + } + super.destroy(); + logger().debug("CYCLE "+getAmas().getCycle(), "Agent %s destroyed.", toString()); + } + + /** + * Get the name of the agent. Useful for visualization, and essential for {@link Percept}. + * @param name + */ + public String getName() { + return name; + } + + /** + * Tell if the agent is dying. A dying agent no longer perform any useful action, but is not yet removed from its system. + * @return + */ + public boolean isDying() { + return dying; + } + + /** + * Set the render strategy of an agent.<br/> + * {@link RenderStrategy#delete()} the old one, and {@link RenderStrategy#initialize()} the new one. + * @param renderStrategy + * @see RenderStrategy + */ + public void setRenderStrategy(RenderStrategy renderStrategy) { + if(this.renderStrategy != null) this.renderStrategy.delete(); + this.renderStrategy = renderStrategy; + if(this.renderStrategy != null) this.renderStrategy.initialize(getAmas().getVUIMulti()); + } + + /** + * Get the render strategy of an agent. + * @return + */ + public RenderStrategy getRenderStrategy() { + return renderStrategy; + } +} diff --git a/AMOEBAonAMAK/src/agents/context/Context.java b/AMOEBAonAMAK/src/agents/context/Context.java index 981e0fb4a6fb88c61dc44f585aa0f1ff98508208..d97077c06cddfafaca7b5f598f9a1c810bdead93 100644 --- a/AMOEBAonAMAK/src/agents/context/Context.java +++ b/AMOEBAonAMAK/src/agents/context/Context.java @@ -588,7 +588,24 @@ public class Context extends AmoebaAgent { HashMap<Percept, Double> request = boundsToRequest(bounds); if(request != null) { - return new EndogenousRequest(request, bounds, 7, new ArrayList<Context>(Arrays.asList(this,ctxt)), REQUEST.OVERLAP); + + double currentDistanceToOraclePrediction = this.getLocalModel().distance(this.getCurrentExperiment()); + double otherContextDistanceToOraclePrediction = ctxt.getLocalModel().distance(ctxt.getCurrentExperiment()); + + Double averageDistanceToOraclePrediction = getAmas().getHeadAgent().getAverageRegressionPerformanceIndicator(); + Double distanceDifference = Math.abs(currentDistanceToOraclePrediction-otherContextDistanceToOraclePrediction); + + getEnvironment().trace(TRACE_LEVEL.DEBUG, new ArrayList<String>( Arrays.asList(this.getName(),"currentDistanceToOraclePrediction",""+ currentDistanceToOraclePrediction,"otherContextDistanceToOraclePrediction",""+ otherContextDistanceToOraclePrediction, "distanceDifference", ""+distanceDifference))); + + if(distanceDifference<averageDistanceToOraclePrediction) { + return new EndogenousRequest(request, bounds, 6, new ArrayList<Context>(Arrays.asList(this,ctxt)), REQUEST.CONCURRENCE); + } + else { + return new EndogenousRequest(request, bounds, 7, new ArrayList<Context>(Arrays.asList(this,ctxt)), REQUEST.CONFLICT); + } + + + } } else if(overlapCounts == getAmas().getPercepts().size()-1 && voidDistances.size() == 1 && getAmas().getCycle() > 750) { @@ -695,10 +712,13 @@ public class Context extends AmoebaAgent { //double minDistanceToOraclePrediction = Math.min(getAmas().getHeadAgent().getDistanceToRegressionAllowed(), getAmas().getHeadAgent().getDistanceToRegressionAllowed()); Double averageDistanceToOraclePrediction = getAmas().getHeadAgent().getAverageRegressionPerformanceIndicator(); - - if((currentDistanceToOraclePrediction<averageDistanceToOraclePrediction) && (otherContextDistanceToOraclePrediction<averageDistanceToOraclePrediction)) { + Double distanceDifference = Math.abs(currentDistanceToOraclePrediction-otherContextDistanceToOraclePrediction); + + getEnvironment().trace(TRACE_LEVEL.DEBUG, new ArrayList<String>( Arrays.asList(this.getName(),"currentDistanceToOraclePrediction",""+ currentDistanceToOraclePrediction,"otherContextDistanceToOraclePrediction",""+ otherContextDistanceToOraclePrediction, "distanceDifference", ""+distanceDifference))); + + if(distanceDifference<averageDistanceToOraclePrediction) { - getEnvironment().trace(TRACE_LEVEL.DEBUG, new ArrayList<String>( Arrays.asList(this.getName(),"currentDistanceToOraclePrediction",""+ currentDistanceToOraclePrediction,"otherContextDistanceToOraclePrediction",""+ otherContextDistanceToOraclePrediction))); + for(Percept pct : ranges.keySet()) { diff --git a/AMOEBAonAMAK/src/agents/head/Head.java b/AMOEBAonAMAK/src/agents/head/Head.java index ce3ab15712a6ebac55b3bedddb3823ef348903b1..eb6087a322cdecd06c7d1b12e2ec5ddf13724010 100644 --- a/AMOEBAonAMAK/src/agents/head/Head.java +++ b/AMOEBAonAMAK/src/agents/head/Head.java @@ -90,6 +90,7 @@ public class Head extends AmoebaAgent { setContextFromPropositionWasSelected(false); getAmas().data.oldOracleValue = getAmas().data.oracleValue; getAmas().data.oracleValue = getAmas().getPerceptions("oracle"); + setAverageRegressionPerformanceIndicator(); /* The head memorize last used context agent */ lastUsedContext = bestContext; @@ -232,13 +233,18 @@ public class Head extends AmoebaAgent { getAmas().data.executionTimes[6]=System.currentTimeMillis()- getAmas().data.executionTimes[6]; - getAmas().data.executionTimes[7]=System.currentTimeMillis(); + getAmas().data.executionTimes[11]=System.currentTimeMillis(); NCSDetection_ChildContext(); + getAmas().data.executionTimes[11]=System.currentTimeMillis()- getAmas().data.executionTimes[11]; + + getAmas().data.executionTimes[12]=System.currentTimeMillis(); + NCSDetection_PotentialRequest(); + getAmas().data.executionTimes[12]=System.currentTimeMillis()- getAmas().data.executionTimes[12]; - //NCSDetection_PotentialRequest(); - //predictionPropagation(); + + getAmas().data.executionTimes[7]=System.currentTimeMillis(); criticalities.addCriticality("spatialCriticality", (getMinMaxVolume() - getVolumeOfAllContexts()) / getMinMaxVolume()); @@ -316,12 +322,13 @@ public class Head extends AmoebaAgent { getAmas().data.executionTimes[7]=System.currentTimeMillis()- getAmas().data.executionTimes[7]; - for(int i = 0 ; i<20;i++) { getAmas().data.executionTimesSums[i] += getAmas().data.executionTimes[i]; } + + } public double getMinMaxVolume() { @@ -1093,32 +1100,21 @@ public class Head extends AmoebaAgent { double minDistanceToOraclePrediction = Double.POSITIVE_INFINITY; for (Context activatedContext : activatedContexts) { + currentDistanceToOraclePrediction = activatedContext.getLocalModel().distance(activatedContext.getCurrentExperiment()); - - - - - currentDistanceToOraclePrediction = activatedContext.getLocalModel() - .distance(activatedContext.getCurrentExperiment()); - getAmas().data.distanceToRegression = currentDistanceToOraclePrediction; getAmas().data.contextNotFinished = false; getEnvironment().trace(TRACE_LEVEL.DEBUG, new ArrayList<String>(Arrays.asList("MODEL DISTANCE", activatedContext.getName(), "" + activatedContext.getLocalModel().distance(activatedContext.getCurrentExperiment())))); if (!activatedContext.getLocalModel().finishedFirstExperiments()) { + activatedContext.getLocalModel().updateModel(activatedContext.getCurrentExperiment(), getAmas().data.learningSpeed); getAmas().data.contextNotFinished = true; - if(getAmas().data.oracleValue>0) { - - System.out.println(activatedContext.getName()); - - - } } - else if (currentDistanceToOraclePrediction < getAverageRegressionPerformanceIndicator()) { //else if (currentDistanceToOraclePrediction < regressionPerformance.getPerformanceIndicator()) { + activatedContext.getLocalModel().updateModel(activatedContext.getCurrentExperiment(), getAmas().data.learningSpeed); if(getAmas().data.oracleValue>0) { @@ -1131,6 +1127,7 @@ public class Head extends AmoebaAgent { if (currentDistanceToOraclePrediction < minDistanceToOraclePrediction) { minDistanceToOraclePrediction = currentDistanceToOraclePrediction; + getAmas().data.distanceToRegression = minDistanceToOraclePrediction; } if (!getAmas().data.contextNotFinished) { @@ -1152,6 +1149,7 @@ public class Head extends AmoebaAgent { activatedContexts.get(i).criticalities.updateMeans(); if (activatedContexts.get(i).criticalities.getCriticalityMean("distanceToRegression") != null) { + activatedContexts.get(i).regressionPerformance.update(activatedContexts.get(i).criticalities.getCriticalityMean("distanceToRegression")); getEnvironment().trace(TRACE_LEVEL.INFORM, new ArrayList<String>(Arrays.asList("UPDATE REGRESSION PERFORMANCE", activatedContexts.get(i).getName(), ""+activatedContexts.get(i).regressionPerformance.getPerformanceIndicator()))); } @@ -2061,7 +2059,7 @@ public class Head extends AmoebaAgent { EndogenousRequest currentRequest = itr.next(); - if(currentRequest.getType() == REQUEST.OVERLAP) { + if(currentRequest.getType() == REQUEST.CONFLICT || currentRequest.getType() == REQUEST.CONCURRENCE) { existingRequestTest = existingRequestTest || currentRequest.testIfContextsAlreadyAsked(request.getAskingContexts()); } @@ -2113,6 +2111,12 @@ public class Head extends AmoebaAgent { public Double getAverageRegressionPerformanceIndicator() { + return getAmas().data.averageRegressionPerformanceIndicator; + + } + + public void setAverageRegressionPerformanceIndicator() { + int numberOfRegressions = 0; if(activatedNeighborsContexts.size()>0) { double meanRegressionPerformanceIndicator = 0.0; @@ -2121,13 +2125,14 @@ public class Head extends AmoebaAgent { numberOfRegressions+=1; } assert numberOfRegressions != 0; - return meanRegressionPerformanceIndicator/numberOfRegressions; + getAmas().data.averageRegressionPerformanceIndicator = (meanRegressionPerformanceIndicator/numberOfRegressions > getAmas().data.initRegressionPerformance) ? meanRegressionPerformanceIndicator/numberOfRegressions : getAmas().data.initRegressionPerformance; } else{ - return getAmas().data.initRegressionPerformance; + getAmas().data.averageRegressionPerformanceIndicator = getAmas().data.initRegressionPerformance; } } + public void proposition(Context c) { activatedContexts.add(c); diff --git a/AMOEBAonAMAK/src/agents/head/REQUEST.java b/AMOEBAonAMAK/src/agents/head/REQUEST.java index 2cb7d78652523f7ce9f3d3fe3b9ed10f03cf4efe..64ec5f2d25528b862f3fb52151d431d68066335c 100644 --- a/AMOEBAonAMAK/src/agents/head/REQUEST.java +++ b/AMOEBAonAMAK/src/agents/head/REQUEST.java @@ -5,6 +5,8 @@ import java.io.Serializable; public enum REQUEST implements Serializable { OVERLAP, + CONFLICT, + CONCURRENCE, VOID, SELF diff --git a/AMOEBAonAMAK/src/experiments/AdvancedMain.java b/AMOEBAonAMAK/src/experiments/AdvancedMain.java index d0267bb169fd4cb920fb0d8ae0dd01ab69ffec4c..39be228a80022f9765d872c9d0e3e7d2362a76c7 100644 --- a/AMOEBAonAMAK/src/experiments/AdvancedMain.java +++ b/AMOEBAonAMAK/src/experiments/AdvancedMain.java @@ -1,122 +1,152 @@ -package experiments; - -import java.io.File; -import java.io.IOException; - -import fr.irit.smac.amak.Configuration; -import fr.irit.smac.amak.tools.Log; -import gui.AmoebaWindow; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; -import javafx.scene.control.Slider; -import kernel.AMOEBA; -import kernel.StudiedSystem; -import kernel.backup.BackupSystem; -import kernel.backup.IBackupSystem; -import kernel.backup.SaveHelperImpl; - -/** - * A more advanced and complete main. - * @author Hugo - * - */ -public class AdvancedMain { - - public static void main(String[] args) throws IOException { - // Instantiating the MainWindow before usage. - // It also allows you to change some of its behavior before creating an AMOEBA. - // If you use Configuration.commandLineMode = True , then you should skip it. - AmoebaWindow.instance(); - example(); - } - - private static void example() throws IOException { - - // Set AMAK configuration before creating an AMOEBA - Configuration.commandLineMode = false; - Configuration.allowedSimultaneousAgentsExecution = 1; - Configuration.waitForGUI = true; - - // Create an AMOEBA - AMOEBA amoeba = new AMOEBA(); - // Create a studied system and add it to the amoeba. - // Adding a studied system to an amoeba allow you to control the learning speed (the simulation : how many cycles per second) - // with amoeba's scheduler, graphically or programmatically. - StudiedSystem studiedSystem = new F_XY_System(50.0); - amoeba.setStudiedSystem(studiedSystem); - // A window appeared, allowing to control the simulation, but if you try to run it - // it will crash (there's no percepts !). We need to load a configuration : - - // Change how new Context are rendered. - //Context.defaultRenderStrategy = NoneRenderer.class; - - // Create a backup system for the AMOEBA - IBackupSystem backupSystem = new BackupSystem(amoeba); - // Load a configuration matching the studied system - File file = new File("resources/twoDimensionsLauncher.xml"); - backupSystem.load(file); - // Note : if you intend to use a SaveHelper, you can use SaveHelper.load instead of a BackupSystem - - // We add an optional saver, allowing us to autosave the amoeba at each cycle. - // The SaveHelper also add graphical tools to save and load AMOEBA's state. - amoeba.saver = new SaveHelperImpl(amoeba); - // Autosave slow execution, if you want fast training, set saver to null, - // or saver.autoSave = false. - - // The amoeba is ready to be used. - // Next we show how to control it with code : - - // We deny the possibility to change simulation speed with the UI - amoeba.allowGraphicalScheduler(false); - // We allow rendering - amoeba.setRenderUpdate(true); - long start = System.currentTimeMillis(); - // We run some learning cycles - int nbCycle = 1000; - for (int i = 0; i < nbCycle; ++i) { - studiedSystem.playOneStep(); - amoeba.learn(studiedSystem.getOutput()); - } - long end = System.currentTimeMillis(); - System.out.println("Done in : " + (end - start) / 1000.0); - - // We create a manual save point - amoeba.saver.newManualSave("TestManualSave"); - - // We set the log level to INFORM, to avoid debug logs that slow down simulation - Log.defaultMinLevel = Log.Level.INFORM; - - // We deactivate rendering - amoeba.setRenderUpdate(false); - // Do some more learning - start = System.currentTimeMillis(); - for (int i = 0; i < nbCycle; ++i) { - studiedSystem.playOneStep(); - amoeba.learn(studiedSystem.getOutput()); - } - end = System.currentTimeMillis(); - System.out.println("Done in : " + (end - start) / 1000.0); - - - // Activate rendering back - amoeba.setRenderUpdate(true); - // After activating rendering we need to update agent's visualization - amoeba.updateAgentsVisualisation(); - // We allow simulation control with the UI - amoeba.allowGraphicalScheduler(true); - - // Exemple for adding a tool in the toolbar - Slider slider = new Slider(0, 10, 0); - slider.setShowTickLabels(true); - slider.setShowTickMarks(true); - slider.valueProperty().addListener(new ChangeListener<Number>() { - @Override - public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) { - System.out.println("new Value "+newValue); - } - }); - AmoebaWindow.addToolbar(slider); - - System.out.println("End main"); - } -} +package experiments; + +import java.io.File; +import java.io.IOException; + +import fr.irit.smac.amak.Configuration; +import fr.irit.smac.amak.tools.Log; +import fr.irit.smac.amak.ui.VUIMulti; +import gui.AmoebaMultiUIWindow; +import gui.AmoebaWindow; +import javafx.application.Application; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.scene.control.Slider; +import javafx.stage.Stage; +import kernel.AMOEBA; +import kernel.StudiedSystem; +import kernel.backup.BackupSystem; +import kernel.backup.IBackupSystem; +import kernel.backup.SaveHelperImpl; + +/** + * A more advanced and complete main. + * @author Hugo + * + */ +public class AdvancedMain extends Application{ + + + public static void main(String[] args) throws IOException { + + // Application.launch(args) launches JavaFX process + // It also allows you to change some of its behavior before creating an AMOEBA. + // If you use Configuration.commandLineMode = True , then you should skip it. + Application.launch(args); + + + } + + @Override + public void start(Stage primaryStage) throws Exception, IOException { + + example(); + + } + + + + private static void example() throws IOException { + + // Set AMAK configuration before creating an AMOEBA + Configuration.commandLineMode = false; + Configuration.allowedSimultaneousAgentsExecution = 1; + Configuration.waitForGUI = true; + Configuration.multiUI = true; + + + VUIMulti amoebaVUI = new VUIMulti("2D"); + AmoebaMultiUIWindow amoebaUI = new AmoebaMultiUIWindow("ELLSA", amoebaVUI); + + // Create an AMOEBA + AMOEBA amoeba = new AMOEBA(amoebaUI, amoebaVUI); + // Create a studied system and add it to the amoeba. + // Adding a studied system to an amoeba allow you to control the learning speed (the simulation : how many cycles per second) + // with amoeba's scheduler, graphically or programmatically. + StudiedSystem studiedSystem = new F_XY_System(50.0); + amoeba.setStudiedSystem(studiedSystem); + // A window appeared, allowing to control the simulation, but if you try to run it + // it will crash (there's no percepts !). We need to load a configuration : + + // Change how new Context are rendered. + //Context.defaultRenderStrategy = NoneRenderer.class; + + // Create a backup system for the AMOEBA + IBackupSystem backupSystem = new BackupSystem(amoeba); + // Load a configuration matching the studied system + File file = new File("resources/twoDimensionsLauncher.xml"); + backupSystem.load(file); + // Note : if you intend to use a SaveHelper, you can use SaveHelper.load instead of a BackupSystem + + // We add an optional saver, allowing us to autosave the amoeba at each cycle. + // The SaveHelper also add graphical tools to save and load AMOEBA's state. + amoeba.saver = new SaveHelperImpl(amoeba); + // Autosave slow execution, if you want fast training, set saver to null, + // or saver.autoSave = false. + + // The amoeba is ready to be used. + // Next we show how to control it with code : + + // We deny the possibility to change simulation speed with the UI + amoeba.allowGraphicalScheduler(false); + // We allow rendering + amoeba.setRenderUpdate(true); + long start = System.currentTimeMillis(); + // We run some learning cycles + int nbCycle = 100; + for (int i = 0; i < nbCycle; ++i) { + System.out.println(i); + studiedSystem.playOneStep(); + amoeba.learn(studiedSystem.getOutput()); + } + long end = System.currentTimeMillis(); + System.out.println("Done in : " + (end - start) / 1000.0); + + // We create a manual save point + amoeba.saver.newManualSave("TestManualSave"); + + // We set the log level to INFORM, to avoid debug logs that slow down simulation + Log.defaultMinLevel = Log.Level.INFORM; + + // We deactivate rendering + amoeba.setRenderUpdate(false); + // Do some more learning + start = System.currentTimeMillis(); + for (int i = 0; i < nbCycle; ++i) { + studiedSystem.playOneStep(); + amoeba.learn(studiedSystem.getOutput()); + } + end = System.currentTimeMillis(); + System.out.println("Done in : " + (end - start) / 1000.0); + + + // Activate rendering back + amoeba.setRenderUpdate(true); + // After activating rendering we need to update agent's visualization + amoeba.updateAgentsVisualisation(); + // We allow simulation control with the UI + amoeba.allowGraphicalScheduler(true); + + // Exemple for adding a tool in the toolbar + Slider slider = new Slider(0, 10, 0); + slider.setShowTickLabels(true); + slider.setShowTickMarks(true); + slider.valueProperty().addListener(new ChangeListener<Number>() { + @Override + public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) { + System.out.println("new Value "+newValue); + } + }); + amoebaUI.addToolbar(slider); + + System.out.println("End main"); + } + + @Override + public void stop() throws Exception { + super.stop(); + System.exit(0); + } + + +} diff --git a/AMOEBAonAMAK/src/experiments/F_XY_System.java b/AMOEBAonAMAK/src/experiments/F_XY_System.java index 8aba6d446a4bb86d6aea058b234028ff40b8adac..b31bceff9afb18fdc815757fce0de6b26ac86f5f 100644 --- a/AMOEBAonAMAK/src/experiments/F_XY_System.java +++ b/AMOEBAonAMAK/src/experiments/F_XY_System.java @@ -48,7 +48,7 @@ public class F_XY_System implements StudiedSystem { out.put("px0", x); out.put("px1", y); - out.put("oracle", 0.0); + out.put("oracle", result); return out; } diff --git a/AMOEBAonAMAK/src/experiments/Main.java b/AMOEBAonAMAK/src/experiments/Main.java index 4cd5643ea4f16c72567fe716d54b72cd9223787b..721e209ebdd996c13a773954808eb66aa9d84c38 100644 --- a/AMOEBAonAMAK/src/experiments/Main.java +++ b/AMOEBAonAMAK/src/experiments/Main.java @@ -39,7 +39,7 @@ public class Main { } System.out.println("Creating the amoeba"); - AMOEBA amoeba = new AMOEBA(configFile, ss); + AMOEBA amoeba = new AMOEBA(null,null,configFile, ss); synchronized (Thread.currentThread()){ try { diff --git a/AMOEBAonAMAK/src/experiments/MinimalMain.java b/AMOEBAonAMAK/src/experiments/MinimalMain.java deleted file mode 100644 index b9b0bde35821831dfe658b762a50be32ceb0540a..0000000000000000000000000000000000000000 --- a/AMOEBAonAMAK/src/experiments/MinimalMain.java +++ /dev/null @@ -1,22 +0,0 @@ -package experiments; - -import kernel.AMOEBA; -import kernel.StudiedSystem; - -/** - * The most minimal main possible producing a functioning amoeba. - * @author Hugo - * - */ -public class MinimalMain { - - public static void main(String[] args) throws InterruptedException { - // create a system to be studied - StudiedSystem studiedSystem = new F_XY_System(50.0); - // create the amoeba - // Make sure the path to the config file is correct. - AMOEBA amoeba = new AMOEBA("resources/twoDimensionsLauncher.xml", studiedSystem); - // a window should have appeared, allowing you to control and visualize the amoeba. - } - -} diff --git a/AMOEBAonAMAK/src/experiments/MinimalMainCommandLineMode.java b/AMOEBAonAMAK/src/experiments/MinimalMainCommandLineMode.java new file mode 100644 index 0000000000000000000000000000000000000000..d57dfb6fab68c4228f1c088e2d2c2cc19b06b0bf --- /dev/null +++ b/AMOEBAonAMAK/src/experiments/MinimalMainCommandLineMode.java @@ -0,0 +1,40 @@ +package experiments; + +import fr.irit.smac.amak.Configuration; +import kernel.AMOEBA; +import kernel.StudiedSystem; + +/** + * The most minimal main possible producing a functioning amoeba. + * @author Hugo + * + */ +public class MinimalMainCommandLineMode { + + public static void main(String[] args) throws InterruptedException { + + Configuration.commandLineMode = true; + + // create a system to be studied + StudiedSystem studiedSystem = new F_XY_System(50.0); + // create the amoeba + // Make sure the path to the config file is correct. + AMOEBA amoeba = new AMOEBA(null,null,"resources/twoDimensionsLauncher.xml", studiedSystem); + // a window should have appeared, allowing you to control and visualize the amoeba. + + // Learning and Request example + long start = System.currentTimeMillis(); + for (int i = 0; i < 1001; ++i) { + studiedSystem.playOneStep(); + amoeba.learn(studiedSystem.getOutput()); + } + long end = System.currentTimeMillis(); + System.out.println("Done in : " + (end - start) + " ms"); + + for (int i = 0; i < 10; ++i) { + studiedSystem.playOneStep(); + System.out.println(amoeba.request(studiedSystem.getOutput())); + } + } + +} diff --git a/AMOEBAonAMAK/src/experiments/MinimalMainUI.java b/AMOEBAonAMAK/src/experiments/MinimalMainUI.java new file mode 100644 index 0000000000000000000000000000000000000000..074596944882d8c54c4c104060d81602d3365698 --- /dev/null +++ b/AMOEBAonAMAK/src/experiments/MinimalMainUI.java @@ -0,0 +1,40 @@ +package experiments; + +import fr.irit.smac.amak.Configuration; +import kernel.AMOEBA; +import kernel.StudiedSystem; + +/** + * The most minimal main possible producing a functioning amoeba. + * @author Hugo + * + */ +public class MinimalMainUI {//TODO + + public static void main(String[] args) throws InterruptedException { + + Configuration.commandLineMode = false; + + // create a system to be studied + StudiedSystem studiedSystem = new F_XY_System(50.0); + // create the amoeba + // Make sure the path to the config file is correct. + AMOEBA amoeba = new AMOEBA(null,null,"resources/twoDimensionsLauncher.xml", studiedSystem); + // a window should have appeared, allowing you to control and visualize the amoeba. + + // Learning and Request example + long start = System.currentTimeMillis(); + for (int i = 0; i < 1001; ++i) { + studiedSystem.playOneStep(); + amoeba.learn(studiedSystem.getOutput()); + } + long end = System.currentTimeMillis(); + System.out.println("Done in : " + (end - start) + " ms"); + + for (int i = 0; i < 10; ++i) { + studiedSystem.playOneStep(); + System.out.println(amoeba.request(studiedSystem.getOutput())); + } + } + +} diff --git a/AMOEBAonAMAK/src/experiments/SimpleReinforcement.java b/AMOEBAonAMAK/src/experiments/SimpleReinforcement.java new file mode 100644 index 0000000000000000000000000000000000000000..db4a991c79b90f5c0afad2ed25f4037653406224 --- /dev/null +++ b/AMOEBAonAMAK/src/experiments/SimpleReinforcement.java @@ -0,0 +1,169 @@ +package experiments; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.HashMap; +import java.util.Random; + +import fr.irit.smac.amak.Configuration; +import fr.irit.smac.amak.ui.drawables.Drawable; +import fr.irit.smac.amak.ui.drawables.DrawableOval; +import gui.AmoebaWindow; +import javafx.scene.paint.Color; +import kernel.AMOEBA; +import kernel.backup.SaveHelperDummy; +import utils.Pair; +import utils.RandomUtils; +import utils.XmlConfigGenerator; + +/** + * Train an amoeba on a simple reinforcement task. + * The goal of the task is to get to the center. When the position of the agent cross 0, it gets a reward of 100. + * The agent can only moves in 2 directions, of a distance of 1. Moving give a reward of -1. + * If the agent moves outside of the allowed range, it gets a reward of -100. + * @author Hugo + * + */ +public class SimpleReinforcement { + + private Random rand = new Random(); + private double x = 0; + private double reward = 0; + private Drawable pos; + + public static void main(String[] args) { + ArrayList<Pair<String, Boolean>> sensors = new ArrayList<>(); + sensors.add(new Pair<String, Boolean>("p1", false)); + sensors.add(new Pair<String, Boolean>("a1", true)); + File config; + try { + config = File.createTempFile("config", "xml"); + XmlConfigGenerator.makeXML(config, sensors); + } catch (IOException e) { + e.printStackTrace(); + System.exit(1); + return; // now compilator know config is initialized + } + + Configuration.commandLineMode = true; + AMOEBA amoeba = new AMOEBA(null,null,config.getAbsolutePath(), null); + amoeba.saver = new SaveHelperDummy(); + SimpleReinforcement env = new SimpleReinforcement(); + + Random r = new Random(); + HashMap<String, Double> state = env.reset(); + HashMap<String, Double> state2; + double explo = 0.5; + for(int i = 0; i < 100; i++) { + boolean done = false; + Deque<HashMap<String, Double>> actions = new ArrayDeque<>(); + //System.out.println("Explore "+i); + int nbStep = 0; + state = env.reset(); + while(!done) { + nbStep++; + if(nbStep > 500) { + done = true; + } + state.remove("oracle"); + state.remove("a1"); + HashMap<String, Double> action = amoeba.maximize(state); + if(r.nextDouble() < 0.5 || action.get("oracle").equals(Double.NEGATIVE_INFINITY) ) { + //System.out.println("Random action"); + action.put("a1", (r.nextBoolean() ? 10.0 : -10.0)); + } + state2 = env.step(action.get("a1")); + + if(state2.get("oracle") != -1.0) { + done = true; + } + + action.put("p1", state.get("p1")); + action.put("oracle", state2.get("oracle")); + //System.out.println(action); + actions.add(action); + + state = state2; + } + + //System.out.println("Learn "+i); + HashMap<String, Double> action = actions.pop(); + double reward = action.get("oracle"); + amoeba.learn(action); + + while(!actions.isEmpty()) { + action = actions.pop(); + reward += action.get("oracle"); + action.put("oracle", reward); + amoeba.learn(action); + } + + if(explo > 0.1) { + explo -= 0.01; + if(explo < 0.1) + explo = 0.1; + } + + System.out.println("Episode "+i+" reward : "+reward+" explo : "+explo); + } + } + + /** + * Must be called AFTER an AMOEBA with GUI + */ + public SimpleReinforcement() { + //Configuration.commandLineMode = false; + //AmoebaWindow instance = AmoebaWindow.instance(); + //pos = new DrawableOval(0.5, 0.5, 1, 1); + //pos.setColor(new Color(0.5, 0.0, 0.0, 0.5)); + //instance.mainVUI.add(pos); + //instance.mainVUI.createAndAddRectangle(-50, -0.25, 100, 0.5); + //instance.mainVUI.createAndAddRectangle(-0.25, -1, 0.5, 2); + + + + } + + public HashMap<String, Double> step(double action){ + if(action == 0.0) action = rand.nextDouble(); + if(action > 0.0) action = Math.ceil(action); + if(action < 0.0 ) action = Math.floor(action); + if(action > 1.0) action = 1.0; + if(action < -1.0) action = -1.0; + double oldX = x; + x = x + action; + if(x < -50.0 || x > 50.0) { + x = RandomUtils.nextDouble(rand, -50.0, Math.nextUp(50.0)); + reward = -100.0; + } else if(x == 0.0 || sign(oldX) != sign(x)) { + // win ! + reward = 1000.0; + x = RandomUtils.nextDouble(rand, -50.0, Math.nextUp(50.0)); + } else { + reward = -1.0; + } + HashMap<String, Double> ret = new HashMap<>(); + ret.put("p1", x); + ret.put("oracle", reward); + //pos.move(x+0.5, 0.5); + return ret; + } + + public HashMap<String, Double> reset(){ + x = RandomUtils.nextDouble(rand, -50.0, Math.nextUp(50.0)); + reward = 0.0; + + HashMap<String, Double> ret = new HashMap<>(); + ret.put("p1", x); + ret.put("oracle", reward); + return ret; + } + + private int sign(double x) { + return x < 0 ? -1 : 1; + } + +} diff --git a/AMOEBAonAMAK/src/experiments/TestingMain.java b/AMOEBAonAMAK/src/experiments/TestingMain.java index 55bfd61be6a785e91185c2cf2eff2c396e810a15..781cfa3e58b079ee3a6820837d44e3ef4451a893 100644 --- a/AMOEBAonAMAK/src/experiments/TestingMain.java +++ b/AMOEBAonAMAK/src/experiments/TestingMain.java @@ -22,7 +22,7 @@ public class TestingMain { StudiedSystem studiedSystem = new NDimCube(50.0, 3); // create the amoeba // Make sure the path to the config file is correct. - AMOEBA amoeba = new AMOEBA("resources/threeDimensionsLauncher.xml", studiedSystem); + AMOEBA amoeba = new AMOEBA(null,null,"resources/threeDimensionsLauncher.xml", studiedSystem); amoeba.saver = new SaveHelperDummy(); // a window should have appeared, allowing you to control and visualize the amoeba. diff --git a/AMOEBAonAMAK/src/experiments/UnityLauncher/Main.java b/AMOEBAonAMAK/src/experiments/UnityLauncher/Main.java index 099acfb8fc051ddd4d8bdc9e921b199dc79844f7..80756bc9c9159796e795102ae6fd2530802cef93 100644 --- a/AMOEBAonAMAK/src/experiments/UnityLauncher/Main.java +++ b/AMOEBAonAMAK/src/experiments/UnityLauncher/Main.java @@ -76,7 +76,7 @@ public class Main implements Runnable { Configuration.waitForGUI = true; Configuration.plotMilliSecondsUpdate = 20000; - AMOEBA amoeba = new AMOEBA(); + AMOEBA amoeba = new AMOEBA(null,null); StudiedSystem studiedSystem = new F_N_Manager(spaceSize, dimension, nbOfModels, normType, randomExploration, explorationIncrement,explorationWidht,limitedToSpaceZone, oracleNoiseRange); amoeba.setStudiedSystem(studiedSystem); IBackupSystem backupSystem = new BackupSystem(amoeba); diff --git a/AMOEBAonAMAK/src/experiments/benchmark/Benchmark.java b/AMOEBAonAMAK/src/experiments/benchmark/Benchmark.java index aae072a3e57df5830cb40fbde7e2ee357f0adfd0..7eb0eeb7e16366c07be8ebff5cd9af53d2d14a2b 100644 --- a/AMOEBAonAMAK/src/experiments/benchmark/Benchmark.java +++ b/AMOEBAonAMAK/src/experiments/benchmark/Benchmark.java @@ -43,7 +43,7 @@ public class Benchmark { private static void execLearn(int nbCycle, String configFile) { System.out.println("Start "+nbCycle+" learning cycles."); - AMOEBA amoeba = new AMOEBA(); + AMOEBA amoeba = new AMOEBA(null,null); BackupSystem bs = new BackupSystem(amoeba); bs.load(new File(configFile)); StudiedSystem ss = new NDimCube(50, amoeba.getPercepts().size()); @@ -67,7 +67,7 @@ public class Benchmark { private static void execRequest(int nbCycle, String configFile) { System.out.println("Start "+nbCycle+" request cycles."); - AMOEBA amoeba = new AMOEBA(); + AMOEBA amoeba = new AMOEBA(null,null); BackupSystem bs = new BackupSystem(amoeba); bs.load(new File(configFile)); StudiedSystem ss = new NDimCube(50, amoeba.getPercepts().size()); diff --git a/AMOEBAonAMAK/src/experiments/benchmark/BenchmarkThreading.java b/AMOEBAonAMAK/src/experiments/benchmark/BenchmarkThreading.java index 37233bbe46470ba06d5d39dbf52cdb5fd8a40b0c..23bce11e9f8b3c719377ad461f8364d73ee74cf7 100644 --- a/AMOEBAonAMAK/src/experiments/benchmark/BenchmarkThreading.java +++ b/AMOEBAonAMAK/src/experiments/benchmark/BenchmarkThreading.java @@ -116,7 +116,7 @@ public class BenchmarkThreading { // setup cache --- (very important to reduce impact of the 1st measure) Configuration.allowedSimultaneousAgentsExecution = 1; StudiedSystem learnSystem = new NDimCube(50.0, 100); - AMOEBA amoeba = new AMOEBA(); + AMOEBA amoeba = new AMOEBA(null,null); amoeba.setStudiedSystem(learnSystem); IBackupSystem backupSystem = new BackupSystem(amoeba); backupSystem.load(file); @@ -127,7 +127,7 @@ public class BenchmarkThreading { for(int thd = 1; thd <= 8; thd *= 2) { Configuration.allowedSimultaneousAgentsExecution = thd; learnSystem = new NDimCube(50.0, 100); - amoeba = new AMOEBA(); + amoeba = new AMOEBA(null,null); backupSystem = new BackupSystem(amoeba); backupSystem.load(file); List<List<Double>> bench = benchmark(amoeba, learnSystem, learnSystem, 0, 10000, 1000, null); diff --git a/AMOEBAonAMAK/src/experiments/nDimensionsLaunchers/F_N_Launcher.java b/AMOEBAonAMAK/src/experiments/nDimensionsLaunchers/F_N_Launcher.java index 300f91f974a5129d9488e6b97388d84df20173ea..f984ebe3760c85448c5f8e3cdfbd7817b1892753 100644 --- a/AMOEBAonAMAK/src/experiments/nDimensionsLaunchers/F_N_Launcher.java +++ b/AMOEBAonAMAK/src/experiments/nDimensionsLaunchers/F_N_Launcher.java @@ -7,10 +7,14 @@ import java.util.ArrayList; import experiments.FILE; import fr.irit.smac.amak.Configuration; +import fr.irit.smac.amak.ui.VUIMulti; +import gui.AmoebaMultiUIWindow; import gui.AmoebaWindow; +import javafx.application.Application; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.scene.control.Slider; +import javafx.stage.Stage; import kernel.AMOEBA; import kernel.StudiedSystem; import kernel.backup.BackupSystem; @@ -21,7 +25,7 @@ import kernel.backup.SaveHelperImpl; /** * The Class BadContextLauncherEasy. */ -public class F_N_Launcher implements Serializable { +public class F_N_Launcher extends Application implements Serializable { public static final double oracleNoiseRange = 0.5; @@ -31,7 +35,7 @@ public class F_N_Launcher implements Serializable { public static final double spaceSize = 50.0 ; public static final int nbOfModels = 3 ; public static final int normType = 2 ; - public static final boolean randomExploration = false; + public static final boolean randomExploration = true; public static final boolean limitedToSpaceZone = true; //public static final double mappingErrorAllowed = 0.07; // BIG SQUARE public static double mappingErrorAllowed = 0.03; // MULTI @@ -43,27 +47,28 @@ public class F_N_Launcher implements Serializable { public static void main(String[] args) throws IOException { - // Instantiating the MainWindow before usage. - // It also allows you to change some of its behavior before creating an AMOEBA. - // If you use Configuration.commandLineMode = True , then you should skip it. - AmoebaWindow.instance(); - launch(); + + + Application.launch(args); + + } + @Override + public void start(Stage arg0) throws Exception { + - public static void launch() throws IOException{ - - - - // Set AMAK configuration before creating an AMOEBA + Configuration.multiUI=true; Configuration.commandLineMode = false; Configuration.allowedSimultaneousAgentsExecution = 1; Configuration.waitForGUI = true; Configuration.plotMilliSecondsUpdate = 20000; - AMOEBA amoeba = new AMOEBA(); + VUIMulti amoebaVUI = new VUIMulti("2D"); + AmoebaMultiUIWindow amoebaUI = new AmoebaMultiUIWindow("ELLSA", amoebaVUI); + AMOEBA amoeba = new AMOEBA(amoebaUI, amoebaVUI); StudiedSystem studiedSystem = new F_N_Manager(spaceSize, dimension, nbOfModels, normType, randomExploration, explorationIncrement,explorationWidht,limitedToSpaceZone, oracleNoiseRange); amoeba.setStudiedSystem(studiedSystem); IBackupSystem backupSystem = new BackupSystem(amoeba); @@ -90,65 +95,66 @@ public class F_N_Launcher implements Serializable { amoeba.getEnvironment().setMappingErrorAllowed(mappingErrorAllowed); } }); - AmoebaWindow.addToolbar(slider); + amoebaUI.addToolbar(slider); studiedSystem.playOneStep(); amoeba.learn(studiedSystem.getOutput()); /* AUTOMATIC */ -// long start = System.currentTimeMillis(); -// for (int i = 0; i < nbCycle; ++i) { -// studiedSystem.playOneStep(); -// amoeba.learn(studiedSystem.getOutput()); -// } -// long end = System.currentTimeMillis(); -// System.out.println("Done in : " + (end - start) ); -// -// start = System.currentTimeMillis(); -// for (int i = 0; i < nbCycle; ++i) { -// studiedSystem.playOneStep(); -// amoeba.request(studiedSystem.getOutput()); -// } -// end = System.currentTimeMillis(); -// System.out.println("Done in : " + (end - start) ); - - -// /* XP PIERRE */ -// -// String fileName = fileName(new ArrayList<String>(Arrays.asList("GaussiennePierre"))); -// -// FILE Pierrefile = new FILE("Pierre",fileName); -// for (int i = 0; i < nbCycle; ++i) { -// studiedSystem.playOneStep(); -// amoeba.learn(studiedSystem.getOutput()); -// if(amoeba.getHeadAgent().isActiveLearning()) { -// studiedSystem.setActiveLearning(true); -// studiedSystem.setSelfRequest(amoeba.getHeadAgent().getSelfRequest()); -// -// } -// } -// -// for (int i = 0; i < 10; ++i) { -// studiedSystem.playOneStep(); -// System.out.println(studiedSystem.getOutput()); -// System.out.println(amoeba.request(studiedSystem.getOutput())); -// -// -// } -// -// Pierrefile.write(new ArrayList<String>(Arrays.asList("ID contexte","Coeff Cte","Coeff X0","Coeff X1","Min Value","Max Value"))); -// -// for(Context ctxt : amoeba.getContexts()) { -// -// writeMessage(Pierrefile, ctxt.toStringArrayPierre()); +// long start = System.currentTimeMillis(); +// for (int i = 0; i < nbCycle; ++i) { +// studiedSystem.playOneStep(); +// amoeba.learn(studiedSystem.getOutput()); +// } +// long end = System.currentTimeMillis(); +// System.out.println("Done in : " + (end - start) ); +// +// start = System.currentTimeMillis(); +// for (int i = 0; i < nbCycle; ++i) { +// studiedSystem.playOneStep(); +// amoeba.request(studiedSystem.getOutput()); +// } +// end = System.currentTimeMillis(); +// System.out.println("Done in : " + (end - start) ); + + +// /* XP PIERRE */ +// +// String fileName = fileName(new ArrayList<String>(Arrays.asList("GaussiennePierre"))); +// +// FILE Pierrefile = new FILE("Pierre",fileName); +// for (int i = 0; i < nbCycle; ++i) { +// studiedSystem.playOneStep(); +// amoeba.learn(studiedSystem.getOutput()); +// if(amoeba.getHeadAgent().isActiveLearning()) { +// studiedSystem.setActiveLearning(true); +// studiedSystem.setSelfRequest(amoeba.getHeadAgent().getSelfRequest()); +// +// } +// } +// +// for (int i = 0; i < 10; ++i) { +// studiedSystem.playOneStep(); +// System.out.println(studiedSystem.getOutput()); +// System.out.println(amoeba.request(studiedSystem.getOutput())); +// +// +// } +// +// Pierrefile.write(new ArrayList<String>(Arrays.asList("ID contexte","Coeff Cte","Coeff X0","Coeff X1","Min Value","Max Value"))); +// +// for(Context ctxt : amoeba.getContexts()) { +// +// writeMessage(Pierrefile, ctxt.toStringArrayPierre()); // -// } -// -// -// Pierrefile.close(); +// } +// +// +// Pierrefile.close(); - } + + public static String fileName(ArrayList<String> infos) { String fileName = ""; @@ -171,4 +177,8 @@ public class F_N_Launcher implements Serializable { file.sendManualMessage(); } + + + + } diff --git a/AMOEBAonAMAK/src/experiments/nDimensionsLaunchers/F_N_LauncherMultiUI.java b/AMOEBAonAMAK/src/experiments/nDimensionsLaunchers/F_N_LauncherMultiUI.java new file mode 100644 index 0000000000000000000000000000000000000000..0256e545391f70cdc2c15000e5f924fc42b6b67c --- /dev/null +++ b/AMOEBAonAMAK/src/experiments/nDimensionsLaunchers/F_N_LauncherMultiUI.java @@ -0,0 +1,458 @@ +package experiments.nDimensionsLaunchers; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.util.ArrayList; + +import experiments.FILE; +import fr.irit.smac.amak.Configuration; +import fr.irit.smac.amak.examples.randomantsMultiUi.AntHillExampleMultiUI; +import fr.irit.smac.amak.examples.randomantsMultiUi.WorldExampleMultiUI; +import fr.irit.smac.amak.ui.AmasMultiUIWindow; +import fr.irit.smac.amak.ui.VUI; +import fr.irit.smac.amak.ui.VUIMulti; +import gui.AmoebaMultiUIWindow; +import gui.AmoebaWindow; +import javafx.application.Application; +import javafx.application.Platform; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.scene.control.Slider; +import javafx.stage.Stage; +import kernel.AMOEBA; +import kernel.StudiedSystem; +import kernel.backup.BackupSystem; +import kernel.backup.IBackupSystem; +import kernel.backup.SaveHelperImpl; +import utils.TRACE_LEVEL; + + +/** + * The Class BadContextLauncherEasy. + */ +public class F_N_LauncherMultiUI extends Application implements Serializable { + + + public static final double oracleNoiseRange = 0.5; + public static final double learningSpeed = 0.01; + public static final int regressionPoints = 100; + public static final int dimension = 2; + public static final double spaceSize = 50.0 ; + public static final int nbOfModels = 3 ; + public static final int normType = 2 ; + public static final boolean randomExploration = true; + public static final boolean limitedToSpaceZone = true; + //public static final double mappingErrorAllowed = 0.07; // BIG SQUARE + public static double mappingErrorAllowed = 0.03; // MULTI + public static final double explorationIncrement = 1.0 ; + public static final double explorationWidht = 0.5 ; + + public static final int nbCycle = 1000; + + AMOEBA amoeba; + StudiedSystem studiedSystem; + VUIMulti amoebaVUI; + AmoebaMultiUIWindow amoebaUI; + + AMOEBA amoeba2; + StudiedSystem studiedSystem2; + VUIMulti amoebaVUI2; + AmoebaMultiUIWindow amoebaUI2; + + public static void main(String[] args) throws IOException { + + + Application.launch(args); + + + } + + @Override + public void start(Stage arg0) throws Exception, IOException { + + Configuration.multiUI=true; + Configuration.commandLineMode = false; + Configuration.allowedSimultaneousAgentsExecution = 1; + Configuration.waitForGUI = true; + Configuration.plotMilliSecondsUpdate = 20000; + + amoebaVUI = new VUIMulti("2D"); + amoebaUI = new AmoebaMultiUIWindow("ELLSA", amoebaVUI); + + + // Exemple for adding a tool in the toolbar + Slider slider = new Slider(0.01, 0.1, mappingErrorAllowed); + slider.setShowTickLabels(true); + slider.setShowTickMarks(true); + + slider.valueProperty().addListener(new ChangeListener<Number>() { + @Override + public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) { + System.out.println("new Value "+newValue); + mappingErrorAllowed = (double)newValue; + amoeba.getEnvironment().setMappingErrorAllowed(mappingErrorAllowed); + } + }); + amoebaUI.addToolbar(slider); + + + amoebaVUI2 = new VUIMulti("2D"); + amoebaUI2 = new AmoebaMultiUIWindow("ELLSA", amoebaVUI2); + + + // Exemple for adding a tool in the toolbar + Slider slider2 = new Slider(0.01, 0.1, mappingErrorAllowed); + slider2.setShowTickLabels(true); + slider2.setShowTickMarks(true); + + slider2.valueProperty().addListener(new ChangeListener<Number>() { + @Override + public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) { + System.out.println("new Value "+newValue); + mappingErrorAllowed = (double)newValue; + amoeba2.getEnvironment().setMappingErrorAllowed(mappingErrorAllowed); + } + }); + amoebaUI2.addToolbar(slider2); + + + + startTask(100, 1000); + startTask2(500, 100); + + + +// VUIMulti amoebaVUI2 = VUIMulti.get("2D"); +// AmoebaMultiUIWindow amoebaUI2 = new AmoebaMultiUIWindow("ELLSA", amoebaVUI2); +// AMOEBA amoeba2 = new AMOEBA(amoebaUI2, amoebaVUI2); +// +// StudiedSystem studiedSystem2 = new F_N_Manager(spaceSize, dimension, nbOfModels, normType, randomExploration, explorationIncrement,explorationWidht,limitedToSpaceZone, oracleNoiseRange); +// amoeba2.setStudiedSystem(studiedSystem2); +// IBackupSystem backupSystem2 = new BackupSystem(amoeba2); +// File file2 = new File("resources/twoDimensionsLauncher.xml"); +// backupSystem2.load(file2); +// +// amoeba2.saver = new SaveHelperImpl(amoeba2); +// amoeba2.allowGraphicalScheduler(true); +// amoeba2.setRenderUpdate(true); +// amoeba2.data.learningSpeed = learningSpeed; +// amoeba2.data.numberOfPointsForRegression = regressionPoints; +// amoeba2.getEnvironment().setMappingErrorAllowed(mappingErrorAllowed); +// +// // Exemple for adding a tool in the toolbar +// Slider slider2 = new Slider(0.01, 0.1, mappingErrorAllowed); +// slider2.setShowTickLabels(true); +// slider2.setShowTickMarks(true); +// +// slider2.valueProperty().addListener(new ChangeListener<Number>() { +// @Override +// public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) { +// System.out.println("new Value "+newValue); +// mappingErrorAllowed = (double)newValue; +// amoeba2.getEnvironment().setMappingErrorAllowed(mappingErrorAllowed); +// } +// }); +// amoebaUI2.addToolbar(slider2); +// +// studiedSystem2.playOneStep(); +// amoeba2.learn(studiedSystem2.getOutput()); + +// try { +// Thread.sleep(2000) ; +// } catch (InterruptedException e) { +// // gestion de l'erreur +// } +// +// long start = System.currentTimeMillis(); +// for (int i = 0; i < nbCycle; ++i) { +// studiedSystem.playOneStep(); +// amoeba.learn(studiedSystem.getOutput()); +// } +// long end = System.currentTimeMillis(); +// System.out.println("Done in : " + (end - start) ); + + + + } + + public void startTask(long wait, int cycles) + { + // Create a Runnable + Runnable task = new Runnable() + { + public void run() + { + runTask(wait, cycles); + } + }; + + // Run the task in a background thread + Thread backgroundThread = new Thread(task); + // Terminate the running thread if the application exits + backgroundThread.setDaemon(true); + // Start the thread + backgroundThread.start(); + + + } + + public void startTask2(long wait, int cycles) + { + // Create a Runnable + Runnable task = new Runnable() + { + public void run() + { + runTask2(wait, cycles); + } + }; + + // Run the task in a background thread + Thread backgroundThread = new Thread(task); + // Terminate the running thread if the application exits + backgroundThread.setDaemon(true); + // Start the thread + backgroundThread.start(); + + + } + + public void runTask(long wait, int cycles) + { + + try + { + + // Update the Label on the JavaFx Application Thread + Platform.runLater(new Runnable() + { + @Override + public void run() + { + amoeba = new AMOEBA(amoebaUI, amoebaVUI); + studiedSystem = new F_N_Manager(spaceSize, dimension, nbOfModels, normType, randomExploration, explorationIncrement,explorationWidht,limitedToSpaceZone, oracleNoiseRange); + amoeba.setStudiedSystem(studiedSystem); + IBackupSystem backupSystem = new BackupSystem(amoeba); + File file = new File("resources/twoDimensionsLauncher.xml"); + backupSystem.load(file); + + amoeba.saver = new SaveHelperImpl(amoeba); + amoeba.allowGraphicalScheduler(true); + amoeba.setRenderUpdate(true); + amoeba.data.learningSpeed = learningSpeed; + amoeba.data.numberOfPointsForRegression = regressionPoints; + amoeba.getEnvironment().setMappingErrorAllowed(mappingErrorAllowed); + + } + }); + + Thread.sleep(wait); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + + + + for(int i = 0; i < cycles; i++) + { + try + { + // Get the Status + final String status = "Processing " + i + " of " + cycles; + + // Update the Label on the JavaFx Application Thread + Platform.runLater(new Runnable() + { + @Override + public void run() + { + studiedSystem.playOneStep(); + amoeba.learn(studiedSystem.getOutput()); + if(amoeba.getHeadAgent().isActiveLearning()) { + studiedSystem.setActiveLearning(true); + studiedSystem.setSelfRequest(amoeba.getHeadAgent().getSelfRequest()); + + } + System.out.println(status); + } + }); + + Thread.sleep(wait); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + } + } + + public void runTask2(long wait, int cycles) + { + + try + { + + // Update the Label on the JavaFx Application Thread + Platform.runLater(new Runnable() + { + @Override + public void run() + { + amoeba2 = new AMOEBA(amoebaUI2, amoebaVUI2); + studiedSystem2 = new F_N_Manager(spaceSize, dimension, nbOfModels, normType, randomExploration, explorationIncrement,explorationWidht,limitedToSpaceZone, oracleNoiseRange); + amoeba2.setStudiedSystem(studiedSystem2); + IBackupSystem backupSystem2 = new BackupSystem(amoeba2); + File file2 = new File("resources/twoDimensionsLauncher.xml"); + backupSystem2.load(file2); + + amoeba2.saver = new SaveHelperImpl(amoeba2); + amoeba2.allowGraphicalScheduler(true); + amoeba2.setRenderUpdate(true); + amoeba2.data.learningSpeed = learningSpeed; + amoeba2.data.numberOfPointsForRegression = regressionPoints; + amoeba2.getEnvironment().setMappingErrorAllowed(mappingErrorAllowed); + + } + }); + + Thread.sleep(wait); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + + + + for(int i = 0; i < cycles; i++) + { + try + { + // Get the Status + final String status = "Processing " + i + " of " + cycles; + + // Update the Label on the JavaFx Application Thread + Platform.runLater(new Runnable() + { + @Override + public void run() + { + studiedSystem2.playOneStep(); + amoeba2.learn(studiedSystem2.getOutput()); + if(amoeba2.getHeadAgent().isActiveLearning()) { + studiedSystem2.setActiveLearning(true); + studiedSystem2.setSelfRequest(amoeba2.getHeadAgent().getSelfRequest()); + + } + System.out.println(status); + } + }); + + Thread.sleep(wait); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + } + } + + @Override + public void stop() throws Exception { + super.stop(); + System.exit(0); + } + + public static void launch() throws IOException{ + + + + + + + + + /* AUTOMATIC */ +// long start = System.currentTimeMillis(); +// for (int i = 0; i < nbCycle; ++i) { +// studiedSystem.playOneStep(); +// amoeba.learn(studiedSystem.getOutput()); +// } +// long end = System.currentTimeMillis(); +// System.out.println("Done in : " + (end - start) ); +// +// start = System.currentTimeMillis(); +// for (int i = 0; i < nbCycle; ++i) { +// studiedSystem.playOneStep(); +// amoeba.request(studiedSystem.getOutput()); +// } +// end = System.currentTimeMillis(); +// System.out.println("Done in : " + (end - start) ); + + +// /* XP PIERRE */ +// +// String fileName = fileName(new ArrayList<String>(Arrays.asList("GaussiennePierre"))); +// +// FILE Pierrefile = new FILE("Pierre",fileName); +// for (int i = 0; i < nbCycle; ++i) { +// studiedSystem.playOneStep(); +// amoeba.learn(studiedSystem.getOutput()); +// if(amoeba.getHeadAgent().isActiveLearning()) { +// studiedSystem.setActiveLearning(true); +// studiedSystem.setSelfRequest(amoeba.getHeadAgent().getSelfRequest()); +// +// } +// } +// +// for (int i = 0; i < 10; ++i) { +// studiedSystem.playOneStep(); +// System.out.println(studiedSystem.getOutput()); +// System.out.println(amoeba.request(studiedSystem.getOutput())); +// +// +// } +// +// Pierrefile.write(new ArrayList<String>(Arrays.asList("ID contexte","Coeff Cte","Coeff X0","Coeff X1","Min Value","Max Value"))); +// +// for(Context ctxt : amoeba.getContexts()) { +// +// writeMessage(Pierrefile, ctxt.toStringArrayPierre()); +// +// } +// +// +// Pierrefile.close(); + + + } + + public static String fileName(ArrayList<String> infos) { + String fileName = ""; + + for(String info : infos) { + fileName += info + "_"; + } + + return fileName; + } + + public static void writeMessage(FILE file, ArrayList<String> message) { + + file.initManualMessage(); + + for(String m : message) { + file.addManualMessage(m); + } + + file.sendManualMessage(); + + } + + + + +} diff --git a/AMOEBAonAMAK/src/experiments/reinforcement/ReinforcementMultiUI.java b/AMOEBAonAMAK/src/experiments/reinforcement/ReinforcementMultiUI.java new file mode 100644 index 0000000000000000000000000000000000000000..36caa486dc9b19ef94a5d40b915f85c985227a83 --- /dev/null +++ b/AMOEBAonAMAK/src/experiments/reinforcement/ReinforcementMultiUI.java @@ -0,0 +1,617 @@ +package experiments.reinforcement; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Random; + +import agents.context.localModel.TypeLocalModel; +import experiments.FILE; +import experiments.reinforcement.SimpleReinforcement1DSpatialRewardAndActionMltiUI.Environment; +import experiments.reinforcement.SimpleReinforcement1DSpatialRewardAndActionMltiUI.LearningAgent; +import fr.irit.smac.amak.Configuration; +import fr.irit.smac.amak.tools.Log; +import fr.irit.smac.amak.ui.AmasMultiUIWindow; +import fr.irit.smac.amak.ui.VUI; +import fr.irit.smac.amak.ui.VUIMulti; +import fr.irit.smac.amak.ui.drawables.Drawable; +import gui.AmoebaMultiUIWindow; +import gui.AmoebaWindow; +import javafx.application.Application; +import javafx.application.Platform; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.scene.control.Slider; +import javafx.stage.Stage; +import kernel.AMOEBA; +import kernel.StudiedSystem; +import kernel.World; +import kernel.backup.BackupSystem; +import kernel.backup.IBackupSystem; +import kernel.backup.SaveHelperDummy; +import kernel.backup.SaveHelperImpl; +import utils.Pair; +import utils.RandomUtils; +import utils.TRACE_LEVEL; +import utils.XmlConfigGenerator; + + +/** + * The Class BadContextLauncherEasy. + */ +public class ReinforcementMultiUI extends Application implements Serializable { + + + public static final double oracleNoiseRange = 0.5; + public static final double learningSpeed = 0.01; + public static final int regressionPoints = 100; + public static final int dimension = 2; + public static final double spaceSize = 50.0 ; + public static final int nbOfModels = 3 ; + public static final int normType = 2 ; + public static final boolean randomExploration = true; + public static final boolean limitedToSpaceZone = true; + //public static final double mappingErrorAllowed = 0.07; // BIG SQUARE + public static double mappingErrorAllowed = 0.03; // MULTI + public static final double explorationIncrement = 1.0 ; + public static final double explorationWidht = 0.5 ; + + public static final int nbCycle = 1000; + + /* Learn and Test */ + public static final int MAX_STEP_PER_EPISODE = 200; + public static final int N_LEARN = 1000;//400 + public static final int N_TEST = 100; + + /* Exploration */ + public static final double MIN_EXPLO_RATE = 0.02; + public static final double EXPLO_RATE_DIMINUTION_FACTOR = 0.01; + public static final double EXPLO_RATE_BASE = 1; + + AMOEBA amoebaSpatialReward; + VUIMulti amoebaSpatialRewardVUI; + AmoebaMultiUIWindow amoebaSpatialRewardUI; + + AMOEBA amoebaControlModel; + VUIMulti amoebaControlModelVUI; + AmoebaMultiUIWindow amoebaControlModelUI; + + LearningAgent agent; + Environment env; + + int nbStep; + boolean done; + boolean invalid; + HashMap<String, Double> action; + HashMap<String, Double> state ; + HashMap<String, Double> state2; + double totReward; + + public static void main(String[] args) throws IOException { + + + Application.launch(args); + + + } + + @Override + public void start(Stage arg0) throws Exception, IOException { + + Configuration.multiUI=true; + Configuration.commandLineMode = false; + Configuration.allowedSimultaneousAgentsExecution = 1; + Configuration.waitForGUI = true; + Configuration.plotMilliSecondsUpdate = 20000; + + amoebaSpatialRewardVUI = new VUIMulti("2D"); + amoebaSpatialRewardUI = new AmoebaMultiUIWindow("SPATIAL REWARD", amoebaSpatialRewardVUI); + + + amoebaControlModelVUI = new VUIMulti("2D"); + amoebaControlModelUI = new AmoebaMultiUIWindow("CONTROL MODEL", amoebaControlModelVUI); + + startTask(100, 0); + } + + public void startTask(long wait, int cycles) + { + // Create a Runnable + Runnable task = new Runnable() + { + public void run() + { + runTask(wait, cycles); + } + }; + + // Run the task in a background thread + Thread backgroundThread = new Thread(task); + // Terminate the running thread if the application exits + backgroundThread.setDaemon(true); + // Start the thread + backgroundThread.start(); + + + } + + + public void runTask(long wait, int cycles) + { + +// try +// { +// +// // Update the Label on the JavaFx Application Thread +// Platform.runLater(new Runnable() +// { +// @Override +// public void run() +// { +// agent = new AmoebaRewardAndControl(); +// env = new OneDimensionEnv(10); +// } +// }); +// +// Thread.sleep(wait); +// } +// catch (InterruptedException e) +// { +// e.printStackTrace(); +// } + + + + agent = new AmoebaRewardAndControl(); + env = new OneDimensionEnv(10); + + state = env.reset(); // BUG LAAAAAAAAAAAAAAAA + double explo = EXPLO_RATE_BASE; + for(int i = 0; i < N_LEARN; i++) { + nbStep = 0; + state = env.reset(); + action = new HashMap<String, Double>(); + totReward = 0.0; + + // execute simulation cycles + done = false; + invalid = false; + + + while(!done && !invalid) { + + try + { + + // Update the Label on the JavaFx Application Thread + Platform.runLater(new Runnable() + { + @Override + public void run() + { + nbStep++; + if(nbStep > MAX_STEP_PER_EPISODE) { + invalid = true; + } + state.remove("oracle"); + + action = new HashMap<String, Double>(); + + action = agent.explore(state, env); +// if(rand.nextDouble() < explo) { +// action = agent.explore(state, env); +// } else { +// action = agent.choose(state, env); +// } + + + state2 = env.step(action); // new position with associated reward + + if(state2.get("oracle") != -1.0) { //if goal or end of world + done = true; + } + action.put("p1", state.get("p1")); //add previous state to action + + action.put("oracle", state2.get("oracle")); //add current reward to action + + // state : previous position and associated reward + // state2 : new position with current reward + // action : previous state, current action and current reward + + agent.learn(state, state2, action, done); + totReward += action.get("oracle"); + + state = state2; + } + }); + + Thread.sleep(wait); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + + + + } + + System.out.println("-----------------------------------------------------------------------"); + + // update exploration rate + if(explo > MIN_EXPLO_RATE) { + explo -= EXPLO_RATE_DIMINUTION_FACTOR; + if(explo < MIN_EXPLO_RATE) + explo = MIN_EXPLO_RATE; + } + + System.out.println("Episode "+i+" reward : "+totReward+" explo : "+explo); + //double testAR = test(agent, env, r, N_TEST); + //averageRewards.add(testAR); + + //Scanner scan = new Scanner(System.in); + //scan.nextLine(); + } + + + } + + + + @Override + public void stop() throws Exception { + super.stop(); + System.exit(0); + } + + + + public static String fileName(ArrayList<String> infos) { + String fileName = ""; + + for(String info : infos) { + fileName += info + "_"; + } + + return fileName; + } + + public static void writeMessage(FILE file, ArrayList<String> message) { + + file.initManualMessage(); + + for(String m : message) { + file.addManualMessage(m); + } + + file.sendManualMessage(); + + } + + /** + * An environment in which a LearningAgent reside + * @author Hugo + * + */ + public interface Environment { + public List<String> actionSpace(); + public List<String> perceptionSpace(); + public HashMap<String, Double> reset(); + public HashMap<String, Double> step(HashMap<String, Double> action); + public HashMap<String, Double> randomAction(); + } + + /** + * Wrapper for any kind of learning agent + * @author Hugo + * + */ + public interface LearningAgent { + public HashMap<String, Double> choose(HashMap<String, Double> state, Environment env); + public HashMap<String, Double> explore(HashMap<String, Double> state, Environment env); + public void learn(HashMap<String, Double> state, HashMap<String, Double> state2, HashMap<String, Double> action, boolean done); + } + + public class AmoebaRewardAndControl implements LearningAgent { + public double lr = 0.8; + public double gamma = 0.9; + private Random rand = new Random(); + + public AmoebaRewardAndControl() { + amoebaSpatialReward = setupSpatialReward(); + amoebaControlModel = setupControlModel(); + } + + @Override + public HashMap<String, Double> choose(HashMap<String, Double> state, Environment env) { + +// HashMap<String, Double> stateWithVizuAdded = new HashMap<String, Double>(state); +// stateWithVizuAdded.put("p2", 0.0); +// stateWithVizuAdded.put("oracle", 0.0); +// HashMap<String, Double> bestFuturePosition = amoebaSpatialReward.reinforcementRequest(stateWithVizuAdded); +// +// HashMap<String, Double> action = new HashMap<String, Double>(); +// if(bestFuturePosition!=null) { +// HashMap<String, Double> requestForControlModel = new HashMap<String, Double>(); +// requestForControlModel.put("pCurrent", state.get("p1")); +// requestForControlModel.put("pGoal", bestFuturePosition.get("p1")); +// +// double bestAction = amoebaControlModel.request(requestForControlModel); +// +// +// action.put("a1", bestAction); +// } +// action = env.randomAction(); +// +// return action; + return null; + } + + @Override + public void learn(HashMap<String, Double> state, HashMap<String, Double> positionAndReward, + HashMap<String, Double> action, boolean done) { + + // state : previous position and associated reward + // state2 : new position with current reward + // action : previous state, current actions and current reward + + HashMap<String, Double> previousStateCurrentStateAction = new HashMap<>(); + previousStateCurrentStateAction.put("pCurrent", action.get("p1")); + previousStateCurrentStateAction.put("pGoal", positionAndReward.get("p1")); + previousStateCurrentStateAction.put("oracle", action.get("a1")); + + + + //System.out.println("ControlModel " + previousStateCurrentStateAction + " ---------------- SIMPLE REIN XP 149"); + //System.out.println("SpatialReward " + positionAndReward + " ---------------- SIMPLE REIN XP 149"); + + amoebaSpatialReward.learn(positionAndReward); + amoebaControlModel.learn(previousStateCurrentStateAction); + + } + + @Override + public HashMap<String, Double> explore(HashMap<String, Double> state, Environment env) { + return env.randomAction(); + } + } + + + + + + public static class OneDimensionEnv implements Environment { + private Random rand = new Random(); + private double x = 0; + private double reward = 0; + private double size; + private Drawable pos; + + public OneDimensionEnv(double envSize) { + + size = envSize; + + + } + + @Override + public HashMap<String, Double> reset(){ + x = RandomUtils.nextDouble(rand, -size, Math.nextUp(size)); + x = Math.round(x); + reward = 0.0; + //pos.move(x+0.5, 0.5); + + HashMap<String, Double> ret = new HashMap<>(); + ret.put("p1", x); + ret.put("oracle", reward); + return ret; + } + + @Override + public HashMap<String, Double> step(HashMap<String, Double> actionMap){ + double action = actionMap.get("a1"); + //if(action == 0.0) action = rand.nextDouble(); + if(action > 0.0) action = Math.ceil(action); + if(action < 0.0 ) action = Math.floor(action); + if(action > 1.0) action = 1.0; + if(action < -1.0) action = -1.0; + double oldX = x; + x = x + action; + + + //System.out.println("ACTIONS " + " a1 " +action + " " + " a2 " + action2); + if(x < -size || x > size) { + reward = -1000.0; + } else if((x == 0.0) || (sign(oldX) != sign(x) )) { + // win ! + reward = 1000.0; + } else { + reward = -1.0; + } + HashMap<String, Double> ret = new HashMap<>(); + ret.put("p1", x); + ret.put("oracle", reward); + //pos.move(x+0.5, 0.5); + return ret; + } + + @Override + public List<String> actionSpace() { + ArrayList<String> l = new ArrayList<>(); + l.add("a1 enum:true {-1, 0, 1}"); + return l; + } + + @Override + public List<String> perceptionSpace() { + ArrayList<String> l = new ArrayList<>(); + l.add("p1 enum:false [-"+size+", "+size+"]"); + return l; + } + + @Override + public HashMap<String, Double> randomAction() { + double a1 = rand.nextBoolean() ? -1 : 1; + + + + HashMap<String, Double> action = new HashMap<String, Double>(); + action.put("a1", a1); + return action; + } + + } + + + + + + private AMOEBA setupSpatialReward() { + ArrayList<Pair<String, Boolean>> sensors = new ArrayList<>(); + sensors.add(new Pair<String, Boolean>("p1", false)); + File config; + try { + config = File.createTempFile("configSpatialReward", "xml"); + XmlConfigGenerator.makeXML(config, sensors); + } catch (IOException e) { + e.printStackTrace(); + System.exit(1); + return null; // now compilator know config is initialized + } + //File config = new File("resources/simpleReinManualTrained.xml"); + + Log.defaultMinLevel = Log.Level.INFORM; + World.minLevel = TRACE_LEVEL.ERROR; + AMOEBA amoeba = new AMOEBA(amoebaSpatialRewardUI, amoebaSpatialRewardVUI, config.getAbsolutePath(), null); + amoeba.saver = new SaveHelperDummy(); + + + + + amoeba.setLocalModel(TypeLocalModel.MILLER_REGRESSION); + amoeba.getEnvironment().setMappingErrorAllowed(0.025); + //amoeba.setReinforcement(true); + + + return amoeba; + } + + + private AMOEBA setupControlModel() { + ArrayList<Pair<String, Boolean>> sensors = new ArrayList<>(); + sensors.add(new Pair<String, Boolean>("pCurrent", false)); + sensors.add(new Pair<String, Boolean>("pGoal", false)); + File config; + try { + config = File.createTempFile("configControlModel", "xml"); + XmlConfigGenerator.makeXML(config, sensors); + } catch (IOException e) { + e.printStackTrace(); + System.exit(1); + return null; // now compilator know config is initialized + } + //File config = new File("resources/simpleReinManualTrained.xml"); + + Log.defaultMinLevel = Log.Level.INFORM; + World.minLevel = TRACE_LEVEL.ERROR; + AMOEBA amoeba = new AMOEBA(amoebaControlModelUI, amoebaControlModelVUI, config.getAbsolutePath(), null); + amoeba.saver = new SaveHelperDummy(); + + + + + amoeba.setLocalModel(TypeLocalModel.MILLER_REGRESSION); + amoeba.getEnvironment().setMappingErrorAllowed(0.025); + + return amoeba; + } + + + private static int sign(double x) { + return x < 0 ? -1 : 1; + } + + /** + * Teach a learning agent on the SimpleReinforcement problem + * @param agent + * @return + */ + public static ArrayList<Double> learning(LearningAgent agent, Environment env){ + ArrayList<Double> averageRewards = new ArrayList<Double>(); + Random rand = new Random(); + + Random r = new Random(); + HashMap<String, Double> state = env.reset(); + HashMap<String, Double> state2; + double explo = EXPLO_RATE_BASE; + for(int i = 0; i < N_LEARN; i++) { + int nbStep = 0; + state = env.reset(); + HashMap<String, Double> action = new HashMap<String, Double>(); + double totReward = 0.0; + + // execute simulation cycles + boolean done = false; + boolean invalid = false; + + + while(!done && !invalid) { + nbStep++; + if(nbStep > MAX_STEP_PER_EPISODE) { + invalid = true; + } + state.remove("oracle"); + + action = new HashMap<String, Double>(); + + action = agent.explore(state, env); +// if(rand.nextDouble() < explo) { +// action = agent.explore(state, env); +// } else { +// action = agent.choose(state, env); +// } + + + state2 = env.step(action); // new position with associated reward + + if(state2.get("oracle") != -1.0) { //if goal or end of world + done = true; + } + action.put("p1", state.get("p1")); //add previous state to action + + action.put("oracle", state2.get("oracle")); //add current reward to action + + // state : previous position and associated reward + // state2 : new position with current reward + // action : previous state, current action and current reward + + agent.learn(state, state2, action, done); + totReward += action.get("oracle"); + + state = state2; + } + + System.out.println("-----------------------------------------------------------------------"); + + // update exploration rate + if(explo > MIN_EXPLO_RATE) { + explo -= EXPLO_RATE_DIMINUTION_FACTOR; + if(explo < MIN_EXPLO_RATE) + explo = MIN_EXPLO_RATE; + } + + System.out.println("Episode "+i+" reward : "+totReward+" explo : "+explo); + //double testAR = test(agent, env, r, N_TEST); + //averageRewards.add(testAR); + + //Scanner scan = new Scanner(System.in); + //scan.nextLine(); + } + + return averageRewards; + } + +} diff --git a/AMOEBAonAMAK/src/experiments/reinforcement/SimpleReinforcement1DSpatialRewardAndActionMltiUI.java b/AMOEBAonAMAK/src/experiments/reinforcement/SimpleReinforcement1DSpatialRewardAndActionMltiUI.java new file mode 100644 index 0000000000000000000000000000000000000000..db5db34acf6a1951f30419533edc72b467bf3344 --- /dev/null +++ b/AMOEBAonAMAK/src/experiments/reinforcement/SimpleReinforcement1DSpatialRewardAndActionMltiUI.java @@ -0,0 +1,693 @@ +package experiments.reinforcement; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Random; + +import agents.context.localModel.TypeLocalModel; +import agents.percept.Percept; +import experiments.nDimensionsLaunchers.F_N_Manager; +import fr.irit.smac.amak.Configuration; +import fr.irit.smac.amak.tools.Log; +import fr.irit.smac.amak.ui.VUIMulti; +import fr.irit.smac.amak.ui.drawables.Drawable; +import gui.AmoebaMultiUIWindow; +import gui.AmoebaWindow; +import javafx.application.Application; +import javafx.application.Platform; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.scene.control.Slider; +import javafx.stage.Stage; +import kernel.AMOEBA; +import kernel.StudiedSystem; +import kernel.World; +import kernel.backup.BackupSystem; +import kernel.backup.IBackupSystem; +import kernel.backup.SaveHelperDummy; +import kernel.backup.SaveHelperImpl; +import utils.Pair; +import utils.RandomUtils; +import utils.TRACE_LEVEL; +import utils.XmlConfigGenerator; + +/** + * Train an amoeba on a simple reinforcement task. + * The goal of the task is to get to the center. When the position of the agent cross 0, it gets a reward of 100. + * The agent can only moves in 2 directions, of a distance of 1. Moving give a reward of -1. + * If the agent moves outside of the allowed range, it gets a reward of -100. + * @author Hugo + * + */ +public abstract class SimpleReinforcement1DSpatialRewardAndActionMltiUI extends Application implements Serializable{ + + /* Learn and Test */ + public static final int MAX_STEP_PER_EPISODE = 200; + public static final int N_LEARN = 1000;//400 + public static final int N_TEST = 100; + + /* Exploration */ + public static final double MIN_EXPLO_RATE = 0.02; + public static final double EXPLO_RATE_DIMINUTION_FACTOR = 0.01; + public static final double EXPLO_RATE_BASE = 1; + + AMOEBA amoebaSpatialReward; + VUIMulti amoebaSpatialRewardVUI; + AmoebaMultiUIWindow amoebaSpatialRewardUI; + + AMOEBA amoebaControlModel; + VUIMulti amoebaControlModelVUI; + AmoebaMultiUIWindow amoebaControlModelUI; + + AMOEBA amoeba; + StudiedSystem studiedSystem; + VUIMulti amoebaVUI; + AmoebaMultiUIWindow amoebaUI; + + + + public static void main(String[] args) throws IOException { + + + Application.launch(args); + + } + + @Override + public void start(Stage arg0) throws Exception, IOException { + + + Configuration.multiUI=true; + Configuration.commandLineMode = false; + Configuration.allowedSimultaneousAgentsExecution = 1; + Configuration.waitForGUI = true; + Configuration.plotMilliSecondsUpdate = 20000; + + amoebaVUI = new VUIMulti("2D"); + amoebaUI = new AmoebaMultiUIWindow("ELLSA", amoebaVUI); + +// amoebaSpatialRewardVUI = new VUIMulti("2D"); +// amoebaSpatialRewardUI = new AmoebaMultiUIWindow("SPATIAL_REWARD", amoebaSpatialRewardVUI); +// +// amoebaControlModelVUI = new VUIMulti("2D"); +// amoebaControlModelUI = new AmoebaMultiUIWindow("CONTROL_MODEL", amoebaControlModelVUI); +// +// + + //startTask(100, 1000); + + + + } + + public void startTask(long wait, int cycles) + { + // Create a Runnable + Runnable task = new Runnable() + { + public void run() + { + runTask(wait, cycles); + } + }; + + // Run the task in a background thread + Thread backgroundThread = new Thread(task); + // Terminate the running thread if the application exits + backgroundThread.setDaemon(true); + // Start the thread + backgroundThread.start(); + + + } + + public void startTask2(long wait, int cycles) + { + // Create a Runnable + Runnable task = new Runnable() + { + public void run() + { + runTask2(wait, cycles); + } + }; + + // Run the task in a background thread + Thread backgroundThread = new Thread(task); + // Terminate the running thread if the application exits + backgroundThread.setDaemon(true); + // Start the thread + backgroundThread.start(); + + + } + + public void runTask(long wait, int cycles) + { + + try + { + + // Update the Label on the JavaFx Application Thread + Platform.runLater(new Runnable() + { + @Override + public void run() + { + + ArrayList<ArrayList<Double>> results = new ArrayList<>(); + //LearningAgent agent = new QLearning(); + LearningAgent agent = new AmoebaQL(); + //LearningAgent agent = new AmoebaCoop(); + Environment env = new OneDimensionEnv(10); + results.add(learning(agent, env)); + + + int nbEpisodes = results.get(0).size(); + for(int i = 0; i < nbEpisodes; i++) { + double average = 0; + for(int j = 0; j < results.size(); j++) { + average += results.get(j).get(i); + } + average /= results.size(); + System.out.println(""+i+"\t"+average); + } + } + }); + + Thread.sleep(wait); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + + + +// for(int i = 0; i < cycles; i++) +// { +// try +// { +// // Get the Status +// final String status = "Processing " + i + " of " + cycles; +// +// // Update the Label on the JavaFx Application Thread +// Platform.runLater(new Runnable() +// { +// @Override +// public void run() +// { +// /// +// } +// }); +// +// Thread.sleep(wait); +// } +// catch (InterruptedException e) +// { +// e.printStackTrace(); +// } +// } + } + + public void runTask2(long wait, int cycles) + { + + try + { + + // Update the Label on the JavaFx Application Thread + Platform.runLater(new Runnable() + { + @Override + public void run() + { + /// + + } + }); + + Thread.sleep(wait); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + + + + for(int i = 0; i < cycles; i++) + { + try + { + // Get the Status + final String status = "Processing " + i + " of " + cycles; + + // Update the Label on the JavaFx Application Thread + Platform.runLater(new Runnable() + { + @Override + public void run() + { + /// + } + }); + + Thread.sleep(wait); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + } + } + + @Override + public void stop() throws Exception { + super.stop(); + System.exit(0); + } + + + + + + + + + + + + /** + * An environment in which a LearningAgent reside + * @author Hugo + * + */ + public interface Environment { + public List<String> actionSpace(); + public List<String> perceptionSpace(); + public HashMap<String, Double> reset(); + public HashMap<String, Double> step(HashMap<String, Double> action); + public HashMap<String, Double> randomAction(); + } + + /** + * Wrapper for any kind of learning agent + * @author Hugo + * + */ + public interface LearningAgent { + public HashMap<String, Double> choose(HashMap<String, Double> state, Environment env); + public HashMap<String, Double> explore(HashMap<String, Double> state, Environment env); + public void learn(HashMap<String, Double> state, HashMap<String, Double> state2, HashMap<String, Double> action, boolean done); + } + + /** + * Compatible only with OneDimensionEnv + * @author Hugo + * + */ + public static class AmoebaQL implements LearningAgent { + public AMOEBA amoebaSpatialReward; + //public AMOEBA amoebaControlModel; + public double lr = 0.8; + public double gamma = 0.9; + private Random rand = new Random(); + + public AmoebaQL() { + amoebaSpatialReward = setupSpatialReward(); + //amoebaControlModel = setupControlModel(); + } + + @Override + public HashMap<String, Double> choose(HashMap<String, Double> state, Environment env) { + +// HashMap<String, Double> stateWithVizuAdded = new HashMap<String, Double>(state); +// stateWithVizuAdded.put("p2", 0.0); +// stateWithVizuAdded.put("oracle", 0.0); +// HashMap<String, Double> bestFuturePosition = amoebaSpatialReward.reinforcementRequest(stateWithVizuAdded); +// +// HashMap<String, Double> action = new HashMap<String, Double>(); +// if(bestFuturePosition!=null) { +// HashMap<String, Double> requestForControlModel = new HashMap<String, Double>(); +// requestForControlModel.put("pCurrent", state.get("p1")); +// requestForControlModel.put("pGoal", bestFuturePosition.get("p1")); +// +// double bestAction = amoebaControlModel.request(requestForControlModel); +// +// +// action.put("a1", bestAction); +// } +// action = env.randomAction(); +// +// return action; + return null; + } + + @Override + public void learn(HashMap<String, Double> state, HashMap<String, Double> positionAndReward, + HashMap<String, Double> action, boolean done) { + + // state : previous position and associated reward + // state2 : new position with current reward + // action : previous state, current actions and current reward + + HashMap<String, Double> previousStateCurrentStateAction = new HashMap<>(); + previousStateCurrentStateAction.put("pCurrent", action.get("p1")); + previousStateCurrentStateAction.put("pGoal", positionAndReward.get("p1")); + previousStateCurrentStateAction.put("oracle", action.get("a1")); + + + + //System.out.println("ControlModel " + previousStateCurrentStateAction + " ---------------- SIMPLE REIN XP 149"); + //System.out.println("SpatialReward " + positionAndReward + " ---------------- SIMPLE REIN XP 149"); + + amoebaSpatialReward.learn(positionAndReward); + //amoebaControlModel.learn(previousStateCurrentStateAction); + + } + + @Override + public HashMap<String, Double> explore(HashMap<String, Double> state, Environment env) { + return env.randomAction(); + } + } + + + + + + public static class OneDimensionEnv implements Environment { + private Random rand = new Random(); + private double x = 0; + private double reward = 0; + private double size; + private Drawable pos; + + public OneDimensionEnv(double envSize) { + + size = envSize; + + if(!Configuration.commandLineMode) { + AmoebaWindow instance = AmoebaWindow.instance(); + //pos = new DrawableOval(0.5, 0.5, 1, 1); + //pos.setColor(new Color(0.5, 0.0, 0.0, 0.5)); + //instance.mainVUI.add(pos); + //instance.mainVUI.createAndAddRectangle(-50, -0.25, 100, 0.5); + //instance.mainVUI.createAndAddRectangle(-0.25, -1, 0.5, 2); + instance.point.hide(); + //instance.rectangle.hide(); + } + } + + @Override + public HashMap<String, Double> reset(){ + x = RandomUtils.nextDouble(rand, -size, Math.nextUp(size)); + x = Math.round(x); + reward = 0.0; + //pos.move(x+0.5, 0.5); + + HashMap<String, Double> ret = new HashMap<>(); + ret.put("p1", x); + ret.put("oracle", reward); + return ret; + } + + @Override + public HashMap<String, Double> step(HashMap<String, Double> actionMap){ + double action = actionMap.get("a1"); + //if(action == 0.0) action = rand.nextDouble(); + if(action > 0.0) action = Math.ceil(action); + if(action < 0.0 ) action = Math.floor(action); + if(action > 1.0) action = 1.0; + if(action < -1.0) action = -1.0; + double oldX = x; + x = x + action; + + + //System.out.println("ACTIONS " + " a1 " +action + " " + " a2 " + action2); + if(x < -size || x > size) { + reward = -1000.0; + } else if((x == 0.0) || (sign(oldX) != sign(x) )) { + // win ! + reward = 1000.0; + } else { + reward = -1.0; + } + HashMap<String, Double> ret = new HashMap<>(); + ret.put("p1", x); + ret.put("oracle", reward); + //pos.move(x+0.5, 0.5); + return ret; + } + + @Override + public List<String> actionSpace() { + ArrayList<String> l = new ArrayList<>(); + l.add("a1 enum:true {-1, 0, 1}"); + return l; + } + + @Override + public List<String> perceptionSpace() { + ArrayList<String> l = new ArrayList<>(); + l.add("p1 enum:false [-"+size+", "+size+"]"); + return l; + } + + @Override + public HashMap<String, Double> randomAction() { + double a1 = rand.nextBoolean() ? -1 : 1; + + + + HashMap<String, Double> action = new HashMap<String, Double>(); + action.put("a1", a1); + return action; + } + + } + + /** + * Setup an amoeba for the SimpleReinforcement problem + * @return + */ + private static AMOEBA setup() { + ArrayList<Pair<String, Boolean>> sensors = new ArrayList<>(); + sensors.add(new Pair<String, Boolean>("p1", false)); + File config; + try { + config = File.createTempFile("config", "xml"); + XmlConfigGenerator.makeXML(config, sensors); + } catch (IOException e) { + e.printStackTrace(); + System.exit(1); + return null; // now compilator know config is initialized + } + //File config = new File("resources/simpleReinManualTrained.xml"); + + Log.defaultMinLevel = Log.Level.INFORM; + World.minLevel = TRACE_LEVEL.ERROR; + AMOEBA amoeba = new AMOEBA(null, null, config.getAbsolutePath(), null); + amoeba.saver = new SaveHelperDummy(); + + + + + return amoeba; + } + + + + private static AMOEBA setupSpatialReward() { + ArrayList<Pair<String, Boolean>> sensors = new ArrayList<>(); + sensors.add(new Pair<String, Boolean>("p1", false)); + File config; + try { + config = File.createTempFile("configSpatialReward", "xml"); + XmlConfigGenerator.makeXML(config, sensors); + } catch (IOException e) { + e.printStackTrace(); + System.exit(1); + return null; // now compilator know config is initialized + } + //File config = new File("resources/simpleReinManualTrained.xml"); + + Log.defaultMinLevel = Log.Level.INFORM; + World.minLevel = TRACE_LEVEL.ERROR; + AMOEBA amoeba = new AMOEBA(null, null, config.getAbsolutePath(), null); + amoeba.saver = new SaveHelperDummy(); + + + + + amoeba.setLocalModel(TypeLocalModel.MILLER_REGRESSION); + amoeba.getEnvironment().setMappingErrorAllowed(0.025); + //amoeba.setReinforcement(true); + + + return amoeba; + } + + + private static AMOEBA setupControlModel() { + ArrayList<Pair<String, Boolean>> sensors = new ArrayList<>(); + sensors.add(new Pair<String, Boolean>("pCurrent", false)); + sensors.add(new Pair<String, Boolean>("pGoal", false)); + File config; + try { + config = File.createTempFile("configControlModel", "xml"); + XmlConfigGenerator.makeXML(config, sensors); + } catch (IOException e) { + e.printStackTrace(); + System.exit(1); + return null; // now compilator know config is initialized + } + //File config = new File("resources/simpleReinManualTrained.xml"); + + Log.defaultMinLevel = Log.Level.INFORM; + World.minLevel = TRACE_LEVEL.ERROR; + AMOEBA amoeba = new AMOEBA(null, null, config.getAbsolutePath(), null); + amoeba.saver = new SaveHelperDummy(); + + + + + amoeba.setLocalModel(TypeLocalModel.MILLER_REGRESSION); + amoeba.getEnvironment().setMappingErrorAllowed(0.025); + + return amoeba; + } + + /** + * Teach a learning agent on the SimpleReinforcement problem + * @param agent + * @return + */ + public static ArrayList<Double> learning(LearningAgent agent, Environment env){ + ArrayList<Double> averageRewards = new ArrayList<Double>(); + Random rand = new Random(); + + Random r = new Random(); + HashMap<String, Double> state = env.reset(); + HashMap<String, Double> state2; + double explo = EXPLO_RATE_BASE; + for(int i = 0; i < N_LEARN; i++) { + int nbStep = 0; + state = env.reset(); + HashMap<String, Double> action = new HashMap<String, Double>(); + double totReward = 0.0; + + // execute simulation cycles + boolean done = false; + boolean invalid = false; + + + while(!done && !invalid) { + nbStep++; + if(nbStep > MAX_STEP_PER_EPISODE) { + invalid = true; + } + state.remove("oracle"); + + action = new HashMap<String, Double>(); + + action = agent.explore(state, env); +// if(rand.nextDouble() < explo) { +// action = agent.explore(state, env); +// } else { +// action = agent.choose(state, env); +// } + + + state2 = env.step(action); // new position with associated reward + + if(state2.get("oracle") != -1.0) { //if goal or end of world + done = true; + } + action.put("p1", state.get("p1")); //add previous state to action + + action.put("oracle", state2.get("oracle")); //add current reward to action + + // state : previous position and associated reward + // state2 : new position with current reward + // action : previous state, current action and current reward + + agent.learn(state, state2, action, done); + totReward += action.get("oracle"); + + state = state2; + } + + System.out.println("-----------------------------------------------------------------------"); + + // update exploration rate + if(explo > MIN_EXPLO_RATE) { + explo -= EXPLO_RATE_DIMINUTION_FACTOR; + if(explo < MIN_EXPLO_RATE) + explo = MIN_EXPLO_RATE; + } + + System.out.println("Episode "+i+" reward : "+totReward+" explo : "+explo); + //double testAR = test(agent, env, r, N_TEST); + //averageRewards.add(testAR); + + //Scanner scan = new Scanner(System.in); + //scan.nextLine(); + } + + return averageRewards; + } + + private static double test(LearningAgent agent, Environment env, Random r, int nbTest) { + HashMap<String, Double> state; + HashMap<String, Double> state2; + double nbPositiveReward = 0.0; + double tot_reward = 0.0; + for(int i = 0; i < nbTest; i++) { + double reward = 0.0; + state = env.reset(); + + // execute simulation cycles + boolean done = false; + int nbStep = 0; + while(!done) { + nbStep++; + if(nbStep > 200) { + done = true; + } + state.remove("oracle"); + HashMap<String, Double> a = agent.choose(state, env); + + state2 = env.step(a); + + if(state2.get("oracle") != -1.0) { + done = true; + } + + reward += state2.get("oracle"); + + state = state2; + } + if(reward > 0) { + nbPositiveReward += 1.0; + } + tot_reward += reward; + } + double averageReward = tot_reward/nbTest; + System.out.println("Test average reward : "+averageReward+" Positive reward %: "+(nbPositiveReward/nbTest)); + + return averageReward; + } + + + + private static int sign(double x) { + return x < 0 ? -1 : 1; + } + +} \ No newline at end of file diff --git a/AMOEBAonAMAK/src/gui/AmoebaMultiUIWindow.java b/AMOEBAonAMAK/src/gui/AmoebaMultiUIWindow.java new file mode 100644 index 0000000000000000000000000000000000000000..511c19cf1f7b1299259d8d0d7b624ae1e547de9e --- /dev/null +++ b/AMOEBAonAMAK/src/gui/AmoebaMultiUIWindow.java @@ -0,0 +1,166 @@ +package gui; + +import java.util.HashMap; + +import javax.management.InstanceAlreadyExistsException; + +import fr.irit.smac.amak.tools.Log; +import fr.irit.smac.amak.tools.RunLaterHelper; +import fr.irit.smac.amak.ui.AmakPlot; +import fr.irit.smac.amak.ui.AmakPlot.ChartType; +import fr.irit.smac.amak.ui.AmasMultiUIWindow; +import fr.irit.smac.amak.ui.MainWindow; +import fr.irit.smac.amak.ui.SchedulerToolbar; +import fr.irit.smac.amak.ui.VUI; +import fr.irit.smac.amak.ui.VUIMulti; +import fr.irit.smac.amak.ui.drawables.Drawable; +import javafx.application.Application; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.scene.control.Button; +import javafx.scene.control.Menu; +import javafx.scene.control.Slider; +import javafx.scene.control.ToggleButton; +import javafx.scene.control.Tooltip; +import javafx.scene.paint.Color; +import kernel.AMOEBA; +import kernel.backup.SaveHelperImpl; + +/** + * The multi window for AMOEBA GUI. + * @author Bruno + * + */ +public class AmoebaMultiUIWindow extends AmasMultiUIWindow{ + + protected HashMap<String, AmakPlot> plots = new HashMap<>(); + + /** + * The main {@link VUI} for AMOEBA, by default it's the 2D representation of the contexts. + */ + public VUIMulti mainVUI; + + public Drawable point; + public Drawable rectangle; + public ToggleButton toggleRender; + public SchedulerToolbar schedulerToolbar; + public DimensionSelector dimensionSelector; + public Menu windowMenu; + + public AmoebaMultiUIWindow(String title, VUIMulti vui) { + super(title); + mainVUI = vui; + } + + public void initialize(AMOEBA amoeba) { + + + mainVUI.setDefaultView(200, 0, 0); + //addTabbedPanel("2D VUI", mainVUI.getPanel()); + + // scheduler toolbar + schedulerToolbar = new SchedulerToolbar("AMOEBA", amoeba.getScheduler()); + addToolbar(schedulerToolbar); + + // plots + point = mainVUI.createAndAddPoint(0, 0); + point.setName("Cursor"); + rectangle = mainVUI.createAndAddRectangle(10, 10, 10, 10); + rectangle.setName("Neighborhood"); + rectangle.setColor(new Color(1, 1, 1, 0)); + + plots.put("This loop NCS", new AmakPlot(this, "This loop NCS", ChartType.LINE, "Cycle", "Number of NCS")); + plots.put("All time NCS", new AmakPlot(this, "All time NCS", ChartType.LINE, "Cycle", "Number of NCS")); + plots.put("Number of agents", new AmakPlot(this, "Number of agents", ChartType.LINE, "Cycle", "Number of agents")); + plots.put("Errors", new AmakPlot(this, "Errors", ChartType.LINE, "Cycle", "Coefficients")); + plots.put("Distances to models", new AmakPlot(this, "Distances to models", ChartType.LINE, "Cycle", "Distances")); + plots.put("Global Mapping Criticality", new AmakPlot(this, "Global Mapping Criticality", ChartType.LINE, "Cycle", "Criticalities")); + plots.put("Time Execution", new AmakPlot(this, "Time Execution", ChartType.LINE, "Cycle", "Times")); + plots.put("Criticalities", new AmakPlot(this, "Criticalities", ChartType.LINE, "Cycle", "Criticalities")); + + // update render button + toggleRender = new ToggleButton("Allow Rendering"); + toggleRender.setOnAction(evt -> { + amoeba.setRenderUpdate(toggleRender.isSelected()); + if(amoeba.isRenderUpdate()) { + amoeba.updateAgentsVisualisation(); + amoeba.nextCycleRunAllAgents(); + } + }); + toggleRender.setSelected(amoeba.isRenderUpdate()); + addToolbar(toggleRender); + + // dimension selector + dimensionSelector = new DimensionSelector(amoeba.getPercepts(), new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent event) { + amoeba.updateAgentsVisualisation(); + } + }); + RunLaterHelper.runLater(()->mainVUI.toolbar.getItems().add(dimensionSelector)); + + // contextMenu "Request Here" on VUI + new ContextMenuVUIMulti(amoeba, mainVUI); //the ContextMenu add itself to the VUI + + // manual save button + addToolbar(newManualSaveButton(amoeba)); + + Slider slider = new Slider(0, 0.1, 0.1); + slider.setShowTickLabels(true); + slider.setShowTickMarks(true); + slider.valueProperty().addListener(new ChangeListener<Number>() { + @Override + public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) { + amoeba.getEnvironment().mappingErrorAllowed = newValue.doubleValue(); + } + }); + addToolbar(slider); + } + + + + /** + * Get an existing {@link AmakPlot}. + * @param name name of the plot to get + * @return an existing plot. + * @see AmoebaMultiUIWindow#addPlot(String, AmakPlot) + */ + public AmakPlot getPlot(String name) { + return plots.get(name); + } + + /** + * Add an {@link AmakPlot} to le map of plots. Allowing for easy access with {@code AmoebaWindow.instance().getPlot(name)} + * @param name name of the plot to add + * @param plot the plot to add + * @see AmoebaMultiUIWindow#getPlot(String) + */ + public void addPlot(String name, AmakPlot plot) { + plots.put(name, plot); + } + + /** + * Create a button 'Quick Save' button, when clicked create a manual save point using an amoeba's saver. + * @param amoeba + * @return + * @see AMOEBA#saver + * @see SaveHelperImpl#newManualSave(String) + */ + public Button newManualSaveButton(AMOEBA amoeba) { + Button button = new Button("Quick save"); + button.setTooltip(new Tooltip("Create a new save point. You will be able to find it in 'Save Explorer' -> 'Manual Saves'")); + button.setOnAction(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent event) { + if(amoeba.saver != null) { + amoeba.saver.newManualSave("manualSaveButton"); + } else { + Log.defaultLog.error("Main Window", "Cannot make a save point of an amoeba without saver"); + } + } + }); + return button; + } +} diff --git a/AMOEBAonAMAK/src/gui/ContextMenuVUI.java b/AMOEBAonAMAK/src/gui/ContextMenuVUI.java index eb47bc31e8ec91b5b6416e88163a31e30a75af14..07d72a504280fdf50a7719a5cdc0869bace1ddb1 100644 --- a/AMOEBAonAMAK/src/gui/ContextMenuVUI.java +++ b/AMOEBAonAMAK/src/gui/ContextMenuVUI.java @@ -6,6 +6,7 @@ import java.util.Optional; import agents.percept.Percept; import fr.irit.smac.amak.tools.Log; import fr.irit.smac.amak.ui.VUI; +import fr.irit.smac.amak.ui.VUIMulti; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.scene.control.Button; diff --git a/AMOEBAonAMAK/src/gui/ContextMenuVUIMulti.java b/AMOEBAonAMAK/src/gui/ContextMenuVUIMulti.java new file mode 100644 index 0000000000000000000000000000000000000000..68999ebe6d9d75dc4948c5ee1644fe17e852fcc1 --- /dev/null +++ b/AMOEBAonAMAK/src/gui/ContextMenuVUIMulti.java @@ -0,0 +1,250 @@ +package gui; + +import java.util.HashMap; +import java.util.Optional; + +import agents.percept.Percept; +import fr.irit.smac.amak.tools.Log; +import fr.irit.smac.amak.ui.VUI; +import fr.irit.smac.amak.ui.VUIMulti; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonBar.ButtonData; +import javafx.scene.control.ButtonType; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.Dialog; +import javafx.scene.control.MenuItem; +import javafx.scene.control.TextField; +import javafx.scene.input.ContextMenuEvent; +import javafx.scene.layout.VBox; +import kernel.AMOEBA; + +/** + * The ContextMenu that is shown when right-clicking the {@link VUI} canvas + * @author Hugo + * + */ +public class ContextMenuVUIMulti extends ContextMenu { + /** + * If true will skip window asking for input in 2D problems + */ + public static boolean quick2DRequest = false; + private double reqHereX; + private double reqHereY; + + /** + * Create a {@link ContextMenu} suited for our needs, composed of 2 items : "Request Here" and "Learn here".<br/> + * Set itself as the vui canvas {@link ContextMenu}. + * @param amoeba the amoeba where {@link AMOEBA#request(HashMap)} and {@link AMOEBA#learn(HashMap)} will be executed. + * @param vui the {@link VUI} hosting the {@link ContextMenuVUIMulti} + */ + public ContextMenuVUIMulti(AMOEBA amoeba, VUIMulti vui) { + // "request here" menu item + setupRequestHereMenuItem(amoeba, vui); + + // "learn here" menu item + setupLearnHereMenuItem(amoeba, vui); + + // show context menu on context menu event from VUI's canvas + vui.getCanvas().setOnContextMenuRequested(new EventHandler<ContextMenuEvent>() { + @Override + public void handle(ContextMenuEvent event) { + reqHereX = event.getX(); + reqHereY = event.getY(); + ContextMenuVUIMulti.this.show(vui.getCanvas(), event.getScreenX(), event.getScreenY()); + } + }); + } + + private void setupRequestHereMenuItem(AMOEBA amoeba, VUIMulti vui) { + MenuItem reqHere = new MenuItem("Request Here"); + reqHere.setOnAction(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent event) { + if(quick2DRequest && amoeba.getPercepts().size() == 2) { + reqTwoDimension(amoeba, vui); + } else { + reqNDimension(amoeba, vui); + } + } + + }); + this.getItems().add(reqHere); + } + + /** + * The "Request Here" action performed when the amoeba is 2D.<br/> + * Execute a {@link AMOEBA#request(HashMap)} at the position of the click. + * @param amoeba + * @param vui + */ + private void reqTwoDimension(AMOEBA amoeba, VUIMulti vui) { + double x = vui.screenToWorldX(reqHereX); + double y = vui.screenToWorldY(reqHereY); + HashMap<String, Double> req = new HashMap<String, Double>(); + req.put(amoeba.getDimensionSelector().d1().getName(), x); + req.put(amoeba.getDimensionSelector().d2().getName(), y); + req.put("oracle", 0.0); + double res = amoeba.request(req); + Log.defaultLog.inform("AMOEBA", "Request Here for x:"+x+" y:"+y+" -> "+res+"."); + } + + /** + * The "Request Here" action performed when the amoeba is not 2D.<br/> + * Show a {@link Dialog} prompting the user to inputs value for the {@link AMOEBA#request(HashMap)}. + * @param amoeba + * @param vui + */ + private void reqNDimension(AMOEBA amoeba, VUIMulti vui) { + double x = vui.screenToWorldX(reqHereX); + double y = vui.screenToWorldY(reqHereY); + + Dialog<HashMap<String, Double>> dialog = new Dialog<>(); + dialog.setTitle("Inputs"); + dialog.setHeaderText("Fill inputs"); + + // Set the button types. + ButtonType okButtonType = new ButtonType("OK", ButtonData.OK_DONE); + dialog.getDialogPane().getButtonTypes().addAll(okButtonType, ButtonType.CANCEL); + + // inputs + HashMap<String, TextField> textFields = new HashMap<>(); + VBox vbox = new VBox(); + for(Percept p : amoeba.getPercepts()) { + TextField tf = new TextField(); + textFields.put(p.getName(), tf); + tf.setPromptText(p.getName()); + if(p.getName().equals(amoeba.getDimensionSelector().d1().getName())) { + tf.setText(x+""); + } + if(p.getName().equals(amoeba.getDimensionSelector().d2().getName())) { + tf.setText(y+""); + } + vbox.getChildren().add(tf); + } + + dialog.getDialogPane().setContent(vbox); + dialog.setResultConverter(dialogButton -> { + if (dialogButton == okButtonType) { + HashMap<String, Double> req = new HashMap<String, Double>(); + for(String k : textFields.keySet()) { + req.put(k, Double.valueOf(textFields.get(k).getText())); + } + req.put("oracle", 0.0); + return req; + } + return null; + }); + + Optional<HashMap<String, Double>> result = dialog.showAndWait(); + result.ifPresent(req -> { + double res = amoeba.request(req); + Log.defaultLog.inform("AMOEBA", "Request Here for "+req+"\n-> "+res+"."); + }); + } + + private void setupLearnHereMenuItem(AMOEBA amoeba, VUIMulti vui) { + MenuItem learnHere = new MenuItem("Learn Here"); + learnHere.setOnAction(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent event) { + if(amoeba.getPercepts().size() == 2) { + learnTwoDimension(amoeba, vui); + } else { + learnNDimebsion(amoeba, vui); + } + } + + }); + this.getItems().add(learnHere); + } + + /** + * The "Learn Here" action performed when the amoeba is 2D.<br/> + * Execute a {@link AMOEBA#learn(HashMap)} at the position of the click. + * @param amoeba + * @param vui + */ + private void learnTwoDimension(AMOEBA amoeba, VUIMulti vui) { + double x = vui.screenToWorldX(reqHereX); + double y = vui.screenToWorldY(reqHereY); + HashMap<String, Double> req = new HashMap<String, Double>(); + req.put(amoeba.getDimensionSelector().d1().getName(), x); + req.put(amoeba.getDimensionSelector().d2().getName(), y); + req.put("oracle", amoeba.studiedSystem.requestOracle(req)); + amoeba.learn(req); + } + + /** + * The "Learn Here" action performed when the amoeba is not 2D.<br/> + * Show a {@link Dialog} prompting the user to inputs value for the {@link AMOEBA#learn(HashMap)}. + * @param amoeba + * @param vui + */ + private void learnNDimebsion(AMOEBA amoeba, VUIMulti vui) { + double x = vui.screenToWorldX(reqHereX); + double y = vui.screenToWorldY(reqHereY); + + Dialog<HashMap<String, Double>> dialog = new Dialog<>(); + dialog.setTitle("Inputs"); + dialog.setHeaderText("Fill inputs"); + + // Set the button types. + ButtonType okButtonType = new ButtonType("OK", ButtonData.OK_DONE); + dialog.getDialogPane().getButtonTypes().addAll(okButtonType, ButtonType.CANCEL); + + // inputs + HashMap<String, TextField> textFields = new HashMap<>(); + VBox vbox = new VBox(); + for(Percept p : amoeba.getPercepts()) { + TextField tf = new TextField(); + textFields.put(p.getName(), tf); + tf.setPromptText(p.getName()); + if(p.getName().equals(amoeba.getDimensionSelector().d1().getName())) { + tf.setText(x+""); + } + if(p.getName().equals(amoeba.getDimensionSelector().d2().getName())) { + tf.setText(y+""); + } + vbox.getChildren().add(tf); + } + + //oracle + TextField oracle = new TextField(); + textFields.put("oracle", oracle); + oracle.setPromptText("oracle"); + Button autoOracle = new Button("Autofill oracle"); + autoOracle.setOnAction(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent event) { + HashMap<String, Double> req = new HashMap<String, Double>(); + for(String k : textFields.keySet()) { + if(!"oracle".equals(k)) { + req.put(k, Double.valueOf(textFields.get(k).getText())); + } + } + oracle.setText(amoeba.studiedSystem.requestOracle(req)+""); + } + }); + vbox.getChildren().addAll(oracle, autoOracle); + + dialog.getDialogPane().setContent(vbox); + dialog.setResultConverter(dialogButton -> { + if (dialogButton == okButtonType) { + HashMap<String, Double> req = new HashMap<String, Double>(); + for(String k : textFields.keySet()) { + req.put(k, Double.valueOf(textFields.get(k).getText())); + } + return req; + } + return null; + }); + + Optional<HashMap<String, Double>> result = dialog.showAndWait(); + result.ifPresent(req -> { + amoeba.learn(req); + Log.defaultLog.inform("AMOEBA", "Learn Here for "+req+" done."); + }); + } +} diff --git a/AMOEBAonAMAK/src/gui/ContextRendererFX.java b/AMOEBAonAMAK/src/gui/ContextRendererFX.java index 23f4160219d97d02da8a8ac0d9914b99ed48b854..b9df13932aa2612a5d0df449891c0683479db291 100644 --- a/AMOEBAonAMAK/src/gui/ContextRendererFX.java +++ b/AMOEBAonAMAK/src/gui/ContextRendererFX.java @@ -2,6 +2,7 @@ package gui; import agents.context.Context; import agents.percept.Percept; +import fr.irit.smac.amak.ui.VUIMulti; import fr.irit.smac.amak.ui.drawables.DrawableRectangle; import gui.utils.ContextColor; import javafx.scene.paint.Color; @@ -99,8 +100,8 @@ public class ContextRendererFX extends RenderStrategy { * window. */ @Override - public void initialize() { - getDrawable().setName(context.toString()); // create the drawable if it does not exist + public void initialize(VUIMulti vui) { + getDrawable(vui).setName(context.toString()); // create the drawable if it does not exist } @@ -116,10 +117,10 @@ public class ContextRendererFX extends RenderStrategy { * * @return */ - public DrawableRectangle getDrawable() { + public DrawableRectangle getDrawable(VUIMulti vui) { if (!context.isDying() && drawable == null) { drawable = new DrawableContext(0, 0, 0, 0, context); - AmoebaWindow.instance().mainVUI.add(drawable); + vui.add(drawable); } return drawable; } diff --git a/AMOEBAonAMAK/src/gui/NoneRenderer.java b/AMOEBAonAMAK/src/gui/NoneRenderer.java index 6d154fdbf4b7ea73541d8e593b34fe66841d0e4c..438a9809fe1a6c41d54ca611719fdcd80e2e25b7 100644 --- a/AMOEBAonAMAK/src/gui/NoneRenderer.java +++ b/AMOEBAonAMAK/src/gui/NoneRenderer.java @@ -1,5 +1,7 @@ package gui; +import fr.irit.smac.amak.ui.VUIMulti; + /** * A render strategy that does nothing. * @author Hugo @@ -12,7 +14,7 @@ public class NoneRenderer extends RenderStrategy { } @Override - public void initialize() { + public void initialize(VUIMulti vui) { } @Override diff --git a/AMOEBAonAMAK/src/gui/RenderStrategy.java b/AMOEBAonAMAK/src/gui/RenderStrategy.java index d6bf4f7424a9b9166fb9d1ffe30ae5a34d740e99..7552c9518486d3050b687bb8f78114e2d274e774 100644 --- a/AMOEBAonAMAK/src/gui/RenderStrategy.java +++ b/AMOEBAonAMAK/src/gui/RenderStrategy.java @@ -1,5 +1,7 @@ package gui; +import fr.irit.smac.amak.ui.VUIMulti; + /** * Strategy on how to render an object. * See {@link ContextRendererFX} for example on how to extends this class. @@ -16,7 +18,8 @@ public abstract class RenderStrategy { /** * Called when the rendered object need to be initialized */ - abstract public void initialize(); + //abstract public void initialize(); + abstract public void initialize(VUIMulti vui); /** * Called to render the object. diff --git a/AMOEBAonAMAK/src/gui/saveExplorer/SaveExplorer.java b/AMOEBAonAMAK/src/gui/saveExplorer/SaveExplorer.java index 1cb91054d89b371ffccabf2fc05bc2551eab408c..325f0160b06ab9f6dad1394f332bd6d0cd057fa1 100644 --- a/AMOEBAonAMAK/src/gui/saveExplorer/SaveExplorer.java +++ b/AMOEBAonAMAK/src/gui/saveExplorer/SaveExplorer.java @@ -257,7 +257,7 @@ public class SaveExplorer extends VBox { */ public static void main(String[] args) throws ClassNotFoundException, IOException { System.out.println("New AMOEBA launched."); - AMOEBA amoeba = new AMOEBA(args[0], (StudiedSystem)SerializeBase64.deserialize(args[1])); + AMOEBA amoeba = new AMOEBA(null,null,args[0], (StudiedSystem)SerializeBase64.deserialize(args[1])); //amoeba.allowGraphicalScheduler(false); for(Percept p : amoeba.getPercepts()) { p.setValue(amoeba.getPerceptions(p.getName())); diff --git a/AMOEBAonAMAK/src/kernel/AMOEBA.java b/AMOEBAonAMAK/src/kernel/AMOEBA.java index 3eef319e9653a6fef3c5c8916ef840a84979110b..3703f834e22b855f9cbafdec95628a492b52a5d9 100644 --- a/AMOEBAonAMAK/src/kernel/AMOEBA.java +++ b/AMOEBAonAMAK/src/kernel/AMOEBA.java @@ -22,6 +22,8 @@ import fr.irit.smac.amak.Scheduling; import fr.irit.smac.amak.tools.Log; import fr.irit.smac.amak.tools.RunLaterHelper; import fr.irit.smac.amak.ui.AmakPlot; +import fr.irit.smac.amak.ui.VUIMulti; +import gui.AmoebaMultiUIWindow; import gui.AmoebaWindow; import gui.DimensionSelector; import kernel.backup.IBackupSystem; @@ -37,6 +39,9 @@ import utils.PrintOnce; */ public class AMOEBA extends Amas<World> implements IAMOEBA { // -- Attributes + + + public VUIMulti vuiMulti; /** * Utility to save, autosave, and load amoebas. */ @@ -47,6 +52,8 @@ public class AMOEBA extends Amas<World> implements IAMOEBA { */ public StudiedSystem studiedSystem; + public AmoebaMultiUIWindow multiUIWindow; + private Head head; private TypeLocalModel localModel = TypeLocalModel.MILLER_REGRESSION; private HashMap<String, Double> perceptions = new HashMap<String, Double>(); @@ -82,8 +89,9 @@ public class AMOEBA extends Amas<World> implements IAMOEBA { * @param studiedSystem * the studied system */ - public AMOEBA() { - super(new World(), Scheduling.HIDDEN); + public AMOEBA(AmoebaMultiUIWindow window, VUIMulti vui) { + super(window, vui, new World(), Scheduling.HIDDEN); + vuiMulti = vui; } /** @@ -91,8 +99,9 @@ public class AMOEBA extends Amas<World> implements IAMOEBA { * * @param path path to the config file. */ - public AMOEBA(String path, StudiedSystem studiedSystem) { - super(new World(), Scheduling.HIDDEN); + public AMOEBA(AmoebaMultiUIWindow window, VUIMulti vui, String path, StudiedSystem studiedSystem) { + super(window, vui, new World(), Scheduling.HIDDEN); + vuiMulti = vui; this.studiedSystem = studiedSystem; setRenderUpdate(true); saver = new SaveHelperImpl(this); @@ -112,23 +121,22 @@ public class AMOEBA extends Amas<World> implements IAMOEBA { @Override protected void onRenderingInitialization() { - AmoebaWindow.instance().initialize(this); + ((AmoebaMultiUIWindow) amasMultiUIWindow).initialize(this); } @Override protected void onUpdateRender() { // Update statistics - if(AmoebaWindow.isInstance()) { - AmoebaWindow window = AmoebaWindow.instance(); - - AmakPlot loopNCS = window.getPlot("This loop NCS"); - AmakPlot allNCS = window.getPlot("All time NCS"); - AmakPlot nbAgent = window.getPlot("Number of agents"); - AmakPlot errors = window.getPlot("Errors"); - AmakPlot distancesToModels = window.getPlot("Distances to models"); - AmakPlot gloabalMappingCriticality = window.getPlot("Global Mapping Criticality"); - AmakPlot timeExecution = window.getPlot("Time Execution"); - AmakPlot criticalities = window.getPlot("Criticalities"); + if(amasMultiUIWindow!=null) { + + AmakPlot loopNCS = ((AmoebaMultiUIWindow)amasMultiUIWindow).getPlot("This loop NCS"); + AmakPlot allNCS = ((AmoebaMultiUIWindow)amasMultiUIWindow).getPlot("All time NCS"); + AmakPlot nbAgent = ((AmoebaMultiUIWindow)amasMultiUIWindow).getPlot("Number of agents"); + AmakPlot errors = ((AmoebaMultiUIWindow)amasMultiUIWindow).getPlot("Errors"); + AmakPlot distancesToModels = ((AmoebaMultiUIWindow)amasMultiUIWindow).getPlot("Distances to models"); + AmakPlot gloabalMappingCriticality = ((AmoebaMultiUIWindow)amasMultiUIWindow).getPlot("Global Mapping Criticality"); + AmakPlot timeExecution = ((AmoebaMultiUIWindow)amasMultiUIWindow).getPlot("Time Execution"); + AmakPlot criticalities = ((AmoebaMultiUIWindow)amasMultiUIWindow).getPlot("Criticalities"); boolean notify = isRenderUpdate(); @@ -165,6 +173,8 @@ public class AMOEBA extends Amas<World> implements IAMOEBA { timeExecution.addData("BestContextInNeighbors", cycle, data.executionTimesSums[8], notify); timeExecution.addData("CreateContext", cycle, data.executionTimesSums[9], notify); timeExecution.addData("UpdateStatitics", cycle, data.executionTimesSums[10], notify); + timeExecution.addData("ChildContext", cycle, data.executionTimesSums[11], notify); + timeExecution.addData("PotentialRequest", cycle, data.executionTimesSums[12], notify); criticalities.addData("Prediction", cycle, data.evolutionCriticalityPrediction, notify); criticalities.addData("Mapping", cycle, data.evolutionCriticalityMapping, notify); @@ -172,7 +182,7 @@ public class AMOEBA extends Amas<World> implements IAMOEBA { } if (isRenderUpdate()) { - AmoebaWindow.instance().mainVUI.updateCanvas(); + ((AmoebaMultiUIWindow)amasMultiUIWindow).mainVUI.updateCanvas(); updateAgentsVisualisation(); RunLaterHelper.runLater(() -> {resetCycleWithoutRender();}); } @@ -467,7 +477,7 @@ public class AMOEBA extends Amas<World> implements IAMOEBA { */ public void allowGraphicalScheduler(boolean allow) { if (!Configuration.commandLineMode) { - AmoebaWindow.instance().schedulerToolbar.setDisable(!allow); + ((AmoebaMultiUIWindow)amasMultiUIWindow).schedulerToolbar.setDisable(!allow); } } @@ -489,7 +499,7 @@ public class AMOEBA extends Amas<World> implements IAMOEBA { super.addPendingAgents(); nextCycleRunAllAgents(); if(!Configuration.commandLineMode) { - AmoebaWindow.instance().dimensionSelector.update(getPercepts()); + ((AmoebaMultiUIWindow)amasMultiUIWindow).dimensionSelector.update(getPercepts()); updateAgentsVisualisation(); } } @@ -525,7 +535,7 @@ public class AMOEBA extends Amas<World> implements IAMOEBA { public void setRenderUpdate(boolean renderUpdate) { if (!Configuration.commandLineMode) { this.renderUpdate = renderUpdate; - AmoebaWindow.instance().toggleRender.setSelected(renderUpdate); + ((AmoebaMultiUIWindow)amasMultiUIWindow).toggleRender.setSelected(renderUpdate); if(renderUpdate == true) nextCycleRunAllAgents(); } @@ -622,13 +632,13 @@ public class AMOEBA extends Amas<World> implements IAMOEBA { for(Agent<? extends Amas<World>, World> a : getAgents()) { a.onUpdateRender(); } - AmoebaWindow.instance().point.move(AmoebaWindow.instance().dimensionSelector.d1().getValue(), AmoebaWindow.instance().dimensionSelector.d2().getValue()); - AmoebaWindow.instance().rectangle.setHeight(2*getEnvironment().getContextCreationNeighborhood(null, AmoebaWindow.instance().dimensionSelector.d2())); - AmoebaWindow.instance().rectangle.setWidth(2*getEnvironment().getContextCreationNeighborhood(null, AmoebaWindow.instance().dimensionSelector.d1())); - AmoebaWindow.instance().rectangle.move(AmoebaWindow.instance().dimensionSelector.d1().getValue() - getEnvironment().getContextCreationNeighborhood(null, AmoebaWindow.instance().dimensionSelector.d1()), AmoebaWindow.instance().dimensionSelector.d2().getValue() - getEnvironment().getContextCreationNeighborhood(null, AmoebaWindow.instance().dimensionSelector.d2())); - AmoebaWindow.instance().mainVUI.updateCanvas(); - AmoebaWindow.instance().point.toFront(); - AmoebaWindow.instance().point.setInfo(getCursorInfo()); + ((AmoebaMultiUIWindow)amasMultiUIWindow).point.move(((AmoebaMultiUIWindow)amasMultiUIWindow).dimensionSelector.d1().getValue(), ((AmoebaMultiUIWindow)amasMultiUIWindow).dimensionSelector.d2().getValue()); + ((AmoebaMultiUIWindow)amasMultiUIWindow).rectangle.setHeight(2*getEnvironment().getContextCreationNeighborhood(null, ((AmoebaMultiUIWindow)amasMultiUIWindow).dimensionSelector.d2())); + ((AmoebaMultiUIWindow)amasMultiUIWindow).rectangle.setWidth(2*getEnvironment().getContextCreationNeighborhood(null, ((AmoebaMultiUIWindow)amasMultiUIWindow).dimensionSelector.d1())); + ((AmoebaMultiUIWindow)amasMultiUIWindow).rectangle.move(((AmoebaMultiUIWindow)amasMultiUIWindow).dimensionSelector.d1().getValue() - getEnvironment().getContextCreationNeighborhood(null, ((AmoebaMultiUIWindow)amasMultiUIWindow).dimensionSelector.d1()), ((AmoebaMultiUIWindow)amasMultiUIWindow).dimensionSelector.d2().getValue() - getEnvironment().getContextCreationNeighborhood(null, ((AmoebaMultiUIWindow)amasMultiUIWindow).dimensionSelector.d2())); + ((AmoebaMultiUIWindow)amasMultiUIWindow).mainVUI.updateCanvas(); + ((AmoebaMultiUIWindow)amasMultiUIWindow).point.toFront(); + ((AmoebaMultiUIWindow)amasMultiUIWindow).point.setInfo(getCursorInfo()); } /** @@ -636,7 +646,7 @@ public class AMOEBA extends Amas<World> implements IAMOEBA { * @return */ public DimensionSelector getDimensionSelector() { - return AmoebaWindow.instance().dimensionSelector; + return ((AmoebaMultiUIWindow)amasMultiUIWindow).dimensionSelector; } /** diff --git a/AMOEBAonAMAK/src/kernel/AmoebaData.java b/AMOEBAonAMAK/src/kernel/AmoebaData.java index 56c0503461b1498a9f9cf26e941897129c2e58e8..35866c4d4f1b7b16c9e8e158dc82d13defa3609c 100644 --- a/AMOEBAonAMAK/src/kernel/AmoebaData.java +++ b/AMOEBAonAMAK/src/kernel/AmoebaData.java @@ -79,4 +79,6 @@ public class AmoebaData implements Serializable { public double[] executionTimesSums = new double[20]; public double initRegressionPerformance = 1.0; + + public double averageRegressionPerformanceIndicator; } \ No newline at end of file diff --git a/AMOEBAonAMAK/src/kernel/backup/SaveHelperImpl.java b/AMOEBAonAMAK/src/kernel/backup/SaveHelperImpl.java index b2258c51d799c99816a3b9d049b84901f5374c1f..152a7433223b7797e72c658192964cb5cbf59d3e 100644 --- a/AMOEBAonAMAK/src/kernel/backup/SaveHelperImpl.java +++ b/AMOEBAonAMAK/src/kernel/backup/SaveHelperImpl.java @@ -11,6 +11,7 @@ import java.util.List; import fr.irit.smac.amak.Configuration; import fr.irit.smac.amak.ui.MainWindow; +import gui.AmoebaMultiUIWindow; import gui.AmoebaWindow; import gui.saveExplorer.SaveExplorer; import javafx.event.ActionEvent; @@ -33,6 +34,9 @@ public class SaveHelperImpl implements ISaveHelper{ public static final String autosaveDirName = "autosave"; public static final String manualsaveDirName = "manual"; + + public AmoebaMultiUIWindow amoebaMultiUIWindow; + /** * The backup system used by the SaveHelper. */ @@ -116,6 +120,51 @@ public class SaveHelperImpl implements ISaveHelper{ setupGraphicalTool(); } } + + public SaveHelperImpl(AMOEBA amoeba, AmoebaMultiUIWindow window) { + amoebaMultiUIWindow = window; + autoSave = !Configuration.commandLineMode; + this.amoeba = amoeba; + backupSystem = new BackupSystem(amoeba); + String dirName = amoeba.toString() + "_" + System.currentTimeMillis(); + dir = Paths.get(savesRoot, dirName); + if (autoSave) { + dirAuto = Paths.get(dir.toString(), autosaveDirName); + try { + Files.createDirectories(dirAuto); + } catch (IOException e) { + e.printStackTrace(); + System.err.println("Cannot create auto save directory. Auto saving is disabled."); + dirAuto = null; + autoSave = false; + } + } + dirManual = Paths.get(dir.toString(), manualsaveDirName); + try { + Files.createDirectories(dirManual); + } catch (IOException e) { + e.printStackTrace(); + System.err.println("Cannot create manual save directory."); + dirManual = null; + } + + // add graphical element if relevant + if (AmoebaWindow.isInstance()) { + SaveExplorer se = new SaveExplorer(amoeba); + AmoebaWindow.addTabbedPanel("Save Explorer", se); + AmoebaWindow.addOnCloseAction(()-> { + if(deleteFolderOnClose) { + try { + DeleteDirectory.deleteDirectoryRecursion(dir); + } catch (IOException e) { + e.printStackTrace(); + System.err.println("Failed to delete saves files on close."); + } + } + }); + setupGraphicalTool(); + } + } @Override public void load(String path) { @@ -187,7 +236,7 @@ public class SaveHelperImpl implements ISaveHelper{ * Add save/load options in the main window. */ private void setupGraphicalTool() { - MainWindow mw = AmoebaWindow.instance(); + AmoebaMultiUIWindow mw = amoebaMultiUIWindow; // TODO remove if they exist items Save and Load in menu Option. FileChooser fileChooser = new FileChooser(); fileChooser.getExtensionFilters().addAll(new FileChooser.ExtensionFilter("XML", "*.xml"), @@ -198,7 +247,7 @@ public class SaveHelperImpl implements ISaveHelper{ @Override public void handle(ActionEvent event) { amoeba.getScheduler().stop(); - File file = fileChooser.showOpenDialog(mw.stage); + File file = fileChooser.showOpenDialog(mw); if (file != null) backupSystem.load(file); } @@ -210,7 +259,7 @@ public class SaveHelperImpl implements ISaveHelper{ @Override public void handle(ActionEvent event) { amoeba.getScheduler().stop(); - File file = fileChooser.showSaveDialog(mw.stage); + File file = fileChooser.showSaveDialog(mw); if (file != null) backupSystem.save(file); } diff --git a/AMOEBAonAMAK/src/ros/Main.java b/AMOEBAonAMAK/src/ros/Main.java index e5d502c6c7e510dba9e4612e0145f5fc8a0561b6..b0a594150f3710bc932385dcdff4813a7769b2f8 100644 --- a/AMOEBAonAMAK/src/ros/Main.java +++ b/AMOEBAonAMAK/src/ros/Main.java @@ -30,7 +30,7 @@ public class Main { } } - AMOEBA amoeba = new AMOEBA(config, null); + AMOEBA amoeba = new AMOEBA(null,null,config, null); amoeba.allowGraphicalScheduler(false); RosBridge bridge = new RosBridge(); diff --git a/README.md b/README.md index fca184d21948de172da6065b449abe7e4266e65a..f53d24a2fa7562aece206214c5727c58fbe7b41b 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,15 @@ # AMAKFX and AMOEBAonAMAK -Tested with openjdk 11. +Tested with OpenJDK 11, should work with Java 8. -## Build Jar with dependencies +Check [the documentation directory](documentation) for more detailed and in depth instruction and explanation. + +Check AMAKFX's [README](AMAKFX/README.md) for more detail on how to use it. + +# Quick start : +## Maven build Jar with dependencies `mvn clean compile assembly:single` -Note that the resulting jars are not platform independant. +Note that the resulting jars are not platform independent. ## Use with Eclipse Clone this repo and import it as a Maven project. @@ -16,5 +21,13 @@ AMOEBA-parent | AMAKFX | AMOEBAonAMAK ``` -For more detail on [AMOEBAonAMAK](AMOEBAonAMAK/README.md) or [AMAKFX](AMAKFX/README.md), check their respective README. +Check AMAKFX's [README](AMAKFX/README.md) for more detail on how to use it. + +Check [the documentation directory](documentation) for more detailed and in depth instruction and explanation on AMOEBA. + +### Amoeba Quick start +```Java +StudiedSystem studiedSystem = new F_XY_System(50.0); +AMOEBA amoeba = new AMOEBA("resources/twoDimensionsLauncher.xml", studiedSystem); +``` \ No newline at end of file diff --git a/documentation/GUI_description.pdf b/documentation/GUI_description.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0b3f72f7104237c4e91c88d76ca29f9c1dd0d728 Binary files /dev/null and b/documentation/GUI_description.pdf differ diff --git a/documentation/GUI_description_1.jpg b/documentation/GUI_description_1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f43f90287ae52c1053b5f30c916b2d9a3b172320 Binary files /dev/null and b/documentation/GUI_description_1.jpg differ diff --git a/documentation/GUI_description_2.jpg b/documentation/GUI_description_2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..69d573416f07c978cff3173c1e19a8d136f57ede Binary files /dev/null and b/documentation/GUI_description_2.jpg differ diff --git a/documentation/gui.md b/documentation/gui.md index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..9e7084ec56fbd2f58616f3fc2f2d229391434175 100644 --- a/documentation/gui.md +++ b/documentation/gui.md @@ -0,0 +1,5 @@ +# GUI description +[Hi-res pdf](GUI_description.pdf) + + + \ No newline at end of file diff --git a/documentation/py4j.md b/documentation/py4j.md new file mode 100644 index 0000000000000000000000000000000000000000..19bcf82b9b08de5529e3ee4f2f86fd0814728dd4 --- /dev/null +++ b/documentation/py4j.md @@ -0,0 +1,11 @@ +# Using Py4j +[Py4j](https://www.py4j.org/) is a Python/Java tool allowing control java code with python. + +Demos are available in the [py4j_demo](py4j_demo) directory. + +A minimalistic main is provided at `py4j.Main.java`. Please note that py4j use socket, so only one process of that main can be executed at a given time. You can have multiple amoeba on a same process, but only one GUI on the same process. If you need to launch multiple amoebas with GUI at a same time, check py4j official documentation on how to change sockets. + +To compile AMOEBA as an executable jar with this main, do : +``` +mvn clean compile assembly:single -Dmain.class=py4j.Main +``` \ No newline at end of file diff --git a/documentation/py4j_demo/README.md b/documentation/py4j_demo/README.md new file mode 100644 index 0000000000000000000000000000000000000000..65c3fe0dcd1f70cd60f98fb59a715da0c932044d --- /dev/null +++ b/documentation/py4j_demo/README.md @@ -0,0 +1,32 @@ +Official py4j documentation [here](https://www.py4j.org/). + +# Install : +Install python dependencies : +``` +pip3 install requirements.txt +``` +Optional : check [openai gym](https://gym.openai.com/) documentation for a full install. + +Run the setup script [setup.sh](setup.sh) (linux only). It just clean some file, compile the correct AMOEBAonAMAK jar and copy it for ease of use. + +You're good to go, check that py4j is correctly working with [basic_demo.py](basic_demo.py) : +``` +python3 basic_demo.py +``` +You should get the classic amoeba's GUI, and random context appearing. + +Then you can look at some actual learning, inside [learn_gym.py](learn_gym.py). + + +# Optional : Ros2Learn +[Ros2Learn](https://github.com/AcutronicRobotics/ros2learn) provide some tools for machine learning on robots, using Ros2, openAI gym, and gazebo. + +Install Ros2 and Ros2learn, follow Ros2Learn instruction. Make sure your version of gazebo is at least 9.9. + +In your python script, import gym_gazebo2. Before running your python code make sure that you properly loaded Ros2 and Ros2learn environment using their provision scripts. + +You can now use gym environment provided by Ros2learn as regular gym environment. +```Python +env = gym.make('MARA-v0') +``` + diff --git a/documentation/py4j_demo/learn_gym.py b/documentation/py4j_demo/learn_gym.py index d21dcae6757358d86309c8bf21068a9c7cca1b70..ffc206c3186b3bcdc945bb21e877a2d40d82e764 100644 --- a/documentation/py4j_demo/learn_gym.py +++ b/documentation/py4j_demo/learn_gym.py @@ -1,6 +1,6 @@ import os import gym -import gym_gazebo2 +#import gym_gazebo2 import subprocess import time import math diff --git a/documentation/save_system.md b/documentation/save_system.md new file mode 100644 index 0000000000000000000000000000000000000000..4219c489f00ceb30e162ed33cdf81f37300279d7 --- /dev/null +++ b/documentation/save_system.md @@ -0,0 +1,54 @@ +#The Save System +The save system is composed of two main elements, and some GUI elements : + +## The Backup System : +Found in `kernel.backup`. + +A backup system is an object responsible for reading/writing the state of amoeba from/to a file. It implement the interface `IBackupSystem`. The implementation provided is `BackupSystem` and use xml to store data. + +Usage : +```Java +AMOEBA amoeba = new AMOEBA() +IBackupSystem backupSystem = new BackupSystem(amoeba); +backupSystem.load(new File("path/to/a/save.xml")); +// do some learning with the amoeba ... +backupSystem.save(new File("path/to/a/new/file.xml")) +``` + +## The Save Helper : +Found in `kernel.backup`. + +A save helper is an object that provide additional functionality over a BackupSystem for the user or other components. Most importantly it : +- Create a temporary directory for saves +- Allow automatic saving during amoeba execution +- Add the options to save/load on the GUI +- Add functionality used by the SaveExplorer. + +See `ISaveHelper` for more. + +Two implementation are provided : the fully functioning `SaveHelperImpl` and `SaveHelperDummy` that do nothing. The dummy is used to deactivate ALL automatic saving, giving a HUGE performance boost. + +Each amoeba has a save helper, available with `amoeba.saver` +```Java +AMOEBA amoeba = new AMOEBA(); +// This constructor initialize the amoeba's saver with a dummy, meaning : +amoeba.saver.save("this_does_not_save"); +// will do nothing + +// or ... +AMOEBA amoeba = new AMOEBA("path/to/save.xml", null); +// This constructor create and use a SaveHelperImpl to initilize the amoeba +amoeba.saver.save("a/valid/path/save.xml"); +// will create a save, and +amoeba.saver.autosave(); +// will create a save in the temporary directory creates by the save helper, located in +System.out.println(SaveHelperImpl.dir) +``` +If amoeba crash, the save files will not be automatically deleted, you may have to manually clean the save directory. + +## The Save Explorer +Found in `gui.saveExplorer`. + +The save explorer read saves from an amoeba's SaveHelper and offer some graphical tool, most importantly the ability to preview a save and quickly cycle trough saves. Allowing to visually play back the execution of an amoeba. + +See the [GUI description](gui.md) for more detail on the GUI. diff --git a/documentation/usage.md b/documentation/usage.md index 352759c9a5788a13213e84dabdc1dcac627f1b07..b7235148d0d75d1ce59ae9429eb6fd0dcb84c012 100644 --- a/documentation/usage.md +++ b/documentation/usage.md @@ -16,7 +16,7 @@ Depending on your problem, determine what AMOEBA should learn, and from what. Bu A config file is a xml file used to initialize your amoeba. Most importantly it contain the list of percepts. You can use `utils/genFiles.py` to generate your xml file based on a list of percept. `python genFiles.py MySystem false px py` will create `MySystem.xml` (and `MySystem.msg`, but ignore it if you don't use [AMOEBA and ROS](rosbridge.md)) containing something looking like the following example : - +(if you don't want to use python, a java class doing the same job is available at `utils.XmlConfigGenerator`) ```xml <?xml version="1.0" encoding="UTF-8"?> <System> @@ -144,6 +144,6 @@ public static void main(String[] args) { } } ``` -We can ask amoeba how well it performed on the last prediction with `amoeba.getHeads().get(0).getCriticity()`. +We can ask amoeba how well it performed on the last prediction with `amoeba.getHeadAgent().getCriticity()`. For more example on how to use amoeba, check `AdvancedMain.java`, `MinimalMain.java`, and `Main.java` in the `experiments` package. \ No newline at end of file