3 Commits 8eed807b93 ... 08883ec5e5

Author SHA1 Message Date
  Henrik Kunzelmann 08883ec5e5 Add multi threading for the sub grid solving 3 years ago
  Henrik Kunzelmann 13e63eddba Fix exit cable rendering 3 years ago
  Henrik Kunzelmann 75c30a895b Use calculateStateAndVisualForCurrentTimeStep and not calculateVisual when updating from async job 3 years ago

+ 2 - 0
src/classes/AbstractCanvasObject.java

@@ -43,6 +43,8 @@ public abstract class AbstractCanvasObject {
 	/* tags that can be used */
 	public Set<Integer> tags = new HashSet<Integer>();
 
+	public Object tag;
+
 	/**
 	 * Constructor for a CpsObejct with an unique ID.
 	 * 

+ 11 - 0
src/classes/Position.java

@@ -32,6 +32,13 @@ public class Position {
 		this.x = (int)p.getX();
 		this.y = (int)p.getY();
 	}
+
+	public Position(Vector2d vector)
+	{
+		this.x = Math.round(vector.getX());
+		this.y = Math.round(vector.getY());
+	}
+
 	/**
 	 * Default constructor without defined position.
 	 */
@@ -91,4 +98,8 @@ public class Position {
 	public String toString() {
 		return "Position[x=" + x + ",y="+ y +"]";
 	}
+
+	public Vector2d toVector() {
+		return new Vector2d(x, y);
+	}
 }

+ 75 - 24
src/holeg/HolegGateway.java

@@ -14,7 +14,8 @@ import ui.controller.FlexManager;
 import ui.controller.SingletonControl;
 import ui.model.*;
 
-import java.util.HashMap;
+import java.util.*;
+import java.util.stream.Collectors;
 
 public class HolegGateway {
     private static Grid convert(MinimumNetwork network, int iteration, FlexManager flexManager) {
@@ -54,7 +55,7 @@ public class HolegGateway {
             SimpleGridNode b = canvasToGrid.get(cable.getModel().getB());
             if (a == null || b == null)
                 continue;
-            SimpleGridEdge edge = gridBuilder.connect(a, b, cable.getModel().getRealLength());
+            SimpleGridEdge edge = gridBuilder.connect(a, b, Math.max(1, cable.getModel().getRealLength()));
             edge.overrideImpedance = new ComplexNumber(cable.getModel().getOverrideResistance(), cable.getModel().getOverrideReactance());
             edge.overrideShuntSusceptance = cable.getModel().getOverrideShuntSusceptance();
             edge.tag = cable;
@@ -93,16 +94,76 @@ public class HolegGateway {
         }
     }
 
-    public static void solve(HolegPowerFlowContext context, MinimumNetwork minimumNetwork, int iteration, FlexManager flexManager, DecoratedNetwork network) {
+    private static synchronized Set<HolegPowerFlowContext> getAllContextFromHolonObjects(List<HolonObject> objects) {
+        HashSet<HolegPowerFlowContext> contextSet = new HashSet<>();
+
+        for (AbstractCanvasObject object : objects)
+            if (object.tag instanceof HolegPowerFlowContext)
+                contextSet.add((HolegPowerFlowContext) object.tag);
+
+        return contextSet;
+    }
+
+    public static synchronized Set<HolegPowerFlowContext> getALlContext() {
+        return HolegGateway.getAllContextFromHolonObjects(SingletonControl.getInstance().getControl().getModel().getAllHolonObjectsOnCanvas());
+    }
+
+    private static synchronized HolegPowerFlowContext getContextFromHolonObjects(List<HolonObject> objects) {
+        if (objects.size() == 0)
+            return null;
+
+        HolegPowerFlowContext context = null;
+        for (AbstractCanvasObject object : objects) {
+            if (object.tag instanceof HolegPowerFlowContext) {
+                if (context == null)
+                    context = (HolegPowerFlowContext) object.tag;
+                else {
+                    context.stopSolver();
+                    object.tag = null;
+                }
+            }
+        }
+
+        if (context == null) {
+            context = new HolegPowerFlowContext();
+            objects.get(0).tag = context;
+        }
+
+        return context;
+    }
+
+    public static synchronized void solve(PowerFlowSettings settings, MinimumNetwork minimumNetwork, int iteration, FlexManager flexManager, DecoratedNetwork network) {
+        // Find context
+        HolegPowerFlowContext context = getContextFromHolonObjects(minimumNetwork.getHolonObjectList());
+        if (context == null)
+            return;
+
+        // Solve with context
+        solve(settings, context, minimumNetwork, iteration, flexManager, network);
+    }
+
+    private static synchronized void solve(PowerFlowSettings settings, HolegPowerFlowContext context, MinimumNetwork minimumNetwork, int iteration, FlexManager flexManager, DecoratedNetwork network) {
+        if (settings == null)
+            throw new IllegalArgumentException("settings is null");
+        if (context == null)
+            throw new IllegalArgumentException("context is null");
+        if (minimumNetwork == null)
+            throw new IllegalArgumentException("minimumNetwork is null");
+        if (network == null)
+            throw new IllegalArgumentException("network is null");
+
+        // If we should show this solved grid
+        if (context.showGridForVisual != null) {
+            decorateNetwork(minimumNetwork, iteration, flexManager, network, context.showGridForVisual);
+            context.showGridForVisual = null;
+            return;
+        }
+
         boolean solve = !PowerFlowAnalysisMenu.getInstance().areUpdatesDisabled();
         Grid grid = convert(minimumNetwork, iteration, flexManager);
 
-        // Create settings when null
-        if (context != null && context.settings == null)
-            context.settings = PowerFlowSettings.getDefault();
-
         // Check if the grid is equal to one already solved
-        if (context != null && context.settings.onlyUpdateGridWhenChanged) {
+        if (settings.onlyUpdateGridWhenChanged) {
             for (Grid lastSolved : context.lastSolvedGrids) {
                 if (GridComparator.isEqual(lastSolved, grid)) {
                     decorateNetwork(minimumNetwork, iteration, flexManager, network, lastSolved);
@@ -122,17 +183,7 @@ public class HolegGateway {
         }
 
         // Stop old solver
-        if (context != null && context.solverJob != null) {
-            try {
-                context.solverJob.interrupt();
-                // wait till old solver job has finished or is interrupted
-                context.solverJob.join(200);
-            }
-            catch(InterruptedException ignored) {
-
-            }
-            context.solverJob = null;
-        }
+        context.stopSolver();
 
         // Create solver job
         Thread solverJob = new Thread(() -> {
@@ -142,16 +193,16 @@ public class HolegGateway {
             // Starting solving when requested
             if (solve) {
                 HolegPowerFlow powerFlow = new HolegPowerFlow();
-                result = powerFlow.solve(grid, context.settings);
+                result = powerFlow.solve(grid, settings);
             }
 
             // Decorate network
             decorateNetwork(minimumNetwork, iteration, flexManager, network, grid);
-            if (context != null)
-                context.solverTimeMilliseconds = (System.nanoTime() - start) / 1e6f;
+            context.solverTimeMilliseconds = (System.nanoTime() - start) / 1e6f;
 
             // Update canvas and other visuals
-            SingletonControl.getInstance().getControl().calculateVisual();
+            context.showGridForVisual = grid;
+            SingletonControl.getInstance().getControl().calculateStateAndVisualForCurrentTimeStep();
 
             // Show result message box
             if (result != null && PowerFlowAnalysisMenu.getInstance().shouldShowResult())
@@ -161,7 +212,7 @@ public class HolegGateway {
 
         // Wait or save solver job
         try {
-            if (context == null || context.settings.waitForSolverJob) {
+            if (settings.waitForSolverJob) {
                 // Start and wait till solver job is finished
                 solverJob.start();
                 solverJob.join();

+ 106 - 20
src/holeg/HolegPowerFlow.java

@@ -13,6 +13,7 @@ import java.io.IOException;
 import java.io.PrintStream;
 import java.nio.charset.StandardCharsets;
 import java.util.*;
+import java.util.concurrent.Semaphore;
 
 public class HolegPowerFlow {
     private Random random = new Random();
@@ -52,6 +53,10 @@ public class HolegPowerFlow {
             PowerFlowProblem bestProblem = null;
             SolverResult bestResult = null;
 
+            // Build all problems
+            PowerFlowProblem[] problems = new PowerFlowProblem[solveTryCount];
+            SolverResult[] results = new SolverResult[solveTryCount];
+
             for (int i = 0; i < solveTryCount; i++) {
                 // Check if we should stop
                 if (Thread.interrupted())
@@ -70,35 +75,116 @@ public class HolegPowerFlow {
                     break;
                 }
 
-                Solver solver = new NewtonRaphsonSolver();
-                SolverResult result = solver.solve(problem, settings.solverSettings);
+                problems[i] = problem;
+                results[i] = null;
+            }
+
+            // Create solver jobs
+            if (!solved) {
+                Semaphore semaphore = new Semaphore(0);
+                Thread[] jobs = new Thread[problems.length];
+                for (int i = 0; i < problems.length; i++) {
+                    int jobID = i;
+                    PowerFlowProblem problem = problems[jobID];
+                    jobs[jobID] = new Thread(() -> {
+                        Solver solver = new NewtonRaphsonSolver();
+                        SolverResult result = solver.solve(problem, settings.solverSettings);
+
+                        synchronized (results) {
+                            results[jobID] = result;
+                        }
+
+                        // Notify main thread that we are done
+                        semaphore.release();
+                    });
+                }
 
-                // Solver was interrupted
-                if (result.error == SolverError.Interrupted)
+                // Check if we should have stopped
+                if (Thread.interrupted())
                     return GridSolverResult.Interrupted;
 
-                // Debug message
-                if (PowerFlowAnalysisMenu.getInstance().shouldShowDebug() && (result.solved || i == solveTryCount - 1))
-                    debugShowResultAsMessageBox(problem, result);
+                boolean[] resultUsed = new boolean[jobs.length];
 
-                if (result.solved) {
-                    solved = true;
+                // Start maximum number of threads
+                int jobThreadCount = Math.min(jobs.length, Math.max(1, settings.maxSolverThreads));
+                for (int i = 0; i < jobThreadCount; i++)
+                    jobs[i].start();
 
-                    // Simply use the first solved result
-                    if (settings.slackNodePlacementStrategy != SlackNodePlacementStrategy.MinimizeSlack) {
-                        bestProblem = problem;
-                        bestResult = result;
-                        break;
+                int nextJob = jobThreadCount;
+                while(true) {
+                    // Wait for any job to finish
+                    try {
+                        semaphore.acquire();
+                    }
+                    catch(InterruptedException ignored) {
+                        for (Thread job : jobs) job.interrupt();
+                        return GridSolverResult.Interrupted;
+                    }
+
+                    // Check results of all jobs
+                    boolean allSolved = true;
+
+                    synchronized (results) {
+                        for (int i = 0; i < jobs.length; i++) {
+                            // Already checked?
+                            if (resultUsed[i])
+                                continue;
+
+                            // Check result of the job
+                            PowerFlowProblem problem = problems[i];
+                            SolverResult result = results[i];
+                            if (result != null) {
+                                // Debug message
+                                if (PowerFlowAnalysisMenu.getInstance().shouldShowDebug() && (result.solved || i == solveTryCount - 1))
+                                    debugShowResultAsMessageBox(problem, result);
+
+                                // Solver was interrupted
+                                if (result.error == SolverError.Interrupted)
+                                    return GridSolverResult.Interrupted;
+
+                                // Solver was successful?
+                                if (result.solved) {
+                                    // Simply use the first solved result
+                                    if (settings.slackNodePlacementStrategy != SlackNodePlacementStrategy.MinimizeSlack) {
+                                        solved = true;
+                                        bestProblem = problem;
+                                        bestResult = result;
+                                        break;
+                                    }
+
+                                    // Is this result better?
+                                    double slackPower = result.flowData.busInjection[0].lenSquared();
+                                    if (slackPower < minSlackPower) {
+                                        minSlackPower = slackPower;
+                                        bestProblem = problem;
+                                        bestResult = result;
+                                    }
+                                }
+                                resultUsed[i] = true;
+
+                            } else
+                                allSolved = false;
+                        }
                     }
 
-                    // Is this result better?
-                    double slackPower = result.flowData.busInjection[0].lenSquared();
-                    if (slackPower < minSlackPower) {
-                        minSlackPower = slackPower;
-                        bestProblem = problem;
-                        bestResult = result;
+                    // Either all are solved or we solved one job
+                    if (allSolved)
+                        solved = true;
+                    if (solved)
+                        break;
+
+                    // Check if we should have stopped
+                    if (Thread.interrupted()) {
+                        for (Thread job : jobs) job.interrupt();
+                        return GridSolverResult.Interrupted;
                     }
+
+                    // Start next thread
+                    if (nextJob < jobs.length)
+                        jobs[nextJob++].start();
                 }
+                // Stop every job
+                for (Thread job : jobs) job.interrupt();
             }
 
             // Update grid if solved

+ 15 - 1
src/holeg/HolegPowerFlowContext.java

@@ -6,10 +6,10 @@ import java.util.ArrayList;
 import java.util.List;
 
 public class HolegPowerFlowContext {
-    public PowerFlowSettings settings = PowerFlowSettings.getDefault();
     public List<Grid> lastSolvedGrids = new ArrayList<>();
     public Thread solverJob;
     public float solverTimeMilliseconds;
+    public Grid showGridForVisual;
 
     public void clearCache() {
         lastSolvedGrids.clear();
@@ -18,4 +18,18 @@ public class HolegPowerFlowContext {
     public boolean isSolving() {
         return solverJob != null && solverJob.isAlive();
     }
+
+    public void stopSolver() {
+        if (solverJob != null) {
+            try {
+                solverJob.interrupt();
+                // wait till old solver job has finished or is interrupted
+                solverJob.join(100);
+            }
+            catch(InterruptedException ignored) {
+
+            }
+            solverJob = null;
+        }
+    }
 }

+ 5 - 0
src/holeg/PowerFlowSettings.java

@@ -11,6 +11,10 @@ public class PowerFlowSettings {
      * When true the solver is only used when the grid has changed to any last solved grid.
      */
     public boolean onlyUpdateGridWhenChanged;
+    /**
+     * Maximum number of solver threads to use for each subgrid.
+     */
+    public int maxSolverThreads;
     /**
      * When true the solving is done synchronous with the main update. This will lead to the main thread waiting.
      */
@@ -41,6 +45,7 @@ public class PowerFlowSettings {
     public PowerFlowSettings() {
         solverSettings = new SolverSettings(); // use default
         onlyUpdateGridWhenChanged = true;
+        maxSolverThreads = 2;
         waitForSolverJob = false;
         skipGridsWithNoProducers = true;
         replaceNodeWithSlackNode = true;

+ 2 - 2
src/holeg/SlackNodePlacementStrategy.java

@@ -6,11 +6,11 @@ public enum SlackNodePlacementStrategy {
      */
     FirstNode,
     /**
-     * A random node is chosen as the slack node. The grid is only solved once.
+     * A random node is chosen as the slack node. The grid is solved multiple times.
      */
     RandomNode,
     /**
-     * A random producer is chosen as the slack node. The grid is only solved once.
+     * A random producer is chosen as the slack node. The grid is solved multiple times.
      */
     RandomProducer,
     /**

+ 7 - 2
src/holeg/ui/PowerFlowAnalysisMenu.java

@@ -1,5 +1,8 @@
 package holeg.ui;
 
+import holeg.HolegGateway;
+import holeg.HolegPowerFlow;
+import holeg.HolegPowerFlowContext;
 import holeg.PowerFlowSettings;
 import ui.controller.SimulationManager;
 import ui.controller.SingletonControl;
@@ -8,6 +11,7 @@ import ui.model.Model;
 import javax.swing.*;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.util.Set;
 
 public class PowerFlowAnalysisMenu extends JMenu {
     private JMenuItem settingsMenu;
@@ -67,7 +71,8 @@ public class PowerFlowAnalysisMenu extends JMenu {
     }
 
     private void clearCache() {
-        SingletonControl.getInstance().getControl().getSimManager().getHolegPowerFlowContext().clearCache();
+        for (HolegPowerFlowContext context : HolegGateway.getALlContext())
+            context.clearCache();
     }
 
     private void solve(boolean clearCache) {
@@ -81,7 +86,7 @@ public class PowerFlowAnalysisMenu extends JMenu {
     }
 
     public PowerFlowSettings getPowerFlowSettings() {
-        return SingletonControl.getInstance().getControl().getSimManager().getHolegPowerFlowContext().settings;
+        return SingletonControl.getInstance().getControl().getSimManager().getHolegPowerFlowSettings();
     }
 
     public boolean areUpdatesDisabled() {

+ 4 - 0
src/holeg/ui/SettingsWindow.java

@@ -34,6 +34,10 @@ public class SettingsWindow extends JDialog {
             settings.slackNodePlacementStrategy = e;
         });
 
+        createNumberTextBox("Solver threads per subgrid", settings.maxSolverThreads, (e) -> {
+            settings.maxSolverThreads = e;
+        });
+
         createNumberTextBox("Max iterations", settings.solverSettings.maxIterations, (e) -> {
             settings.solverSettings.maxIterations = e;
         });

+ 5 - 4
src/ui/controller/SimulationManager.java

@@ -5,6 +5,7 @@ import classes.comparator.EnergyMinToMaxComparator;
 import classes.comparator.MinEnergyComparator;
 import classes.comparator.WeakestBattery;
 import holeg.HolegPowerFlowContext;
+import holeg.PowerFlowSettings;
 import ui.model.IntermediateCableWithState;
 import ui.controller.FlexManager.FlexState;
 import ui.model.DecoratedCable;
@@ -50,7 +51,7 @@ public class SimulationManager {
 	private int timeStep;
 	private FlexiblePane flexPane;
 
-	private HolegPowerFlowContext holegPowerFlowContext = new HolegPowerFlowContext();
+	private PowerFlowSettings holegPowerFlowSettings = PowerFlowSettings.getDefault();
 
 	/**
 	 * Constructor.
@@ -137,7 +138,7 @@ public class SimulationManager {
 		ArrayList<DecoratedNetwork> decorNetworks = new ArrayList<DecoratedNetwork>();
 		FairnessModel actualFairnessModel = model.getFairnessModel();
 		for (MinimumNetwork net : list) {
-			decorNetworks.add(new DecoratedNetwork(holegPowerFlowContext, net, timestep, actualFairnessModel, newFlexManager));
+			decorNetworks.add(new DecoratedNetwork(holegPowerFlowSettings, net, timestep, actualFairnessModel, newFlexManager));
 		}
 
 		
@@ -300,7 +301,7 @@ public class SimulationManager {
 		return savesVisual.getOrDefault(timestep, null);
 	}
 
-	public HolegPowerFlowContext getHolegPowerFlowContext() {
-		return holegPowerFlowContext;
+	public PowerFlowSettings getHolegPowerFlowSettings() {
+		return holegPowerFlowSettings;
 	}
 }

+ 3 - 2
src/ui/model/DecoratedNetwork.java

@@ -7,6 +7,7 @@ import classes.HolonElement;
 import classes.HolonObject;
 import holeg.HolegGateway;
 import holeg.HolegPowerFlowContext;
+import holeg.PowerFlowSettings;
 import ui.controller.FlexManager;
 import ui.model.DecoratedCable.CableState;
 import ui.model.DecoratedHolonObject.HolonObjectState;
@@ -22,7 +23,7 @@ public class DecoratedNetwork {
 
 	private Object lockObject = new Object();
 
-	public DecoratedNetwork(HolegPowerFlowContext context, MinimumNetwork minimumNetwork, int Iteration, FairnessModel actualFairnessModel, FlexManager flexManager){
+	public DecoratedNetwork(PowerFlowSettings settings, MinimumNetwork minimumNetwork, int Iteration, FairnessModel actualFairnessModel, FlexManager flexManager){
 		this.timestep = Iteration;
 		switch(actualFairnessModel) {
 		case AllEqual:
@@ -32,7 +33,7 @@ public class DecoratedNetwork {
 			calculateMinimumDemandFirstNetwork(minimumNetwork, Iteration, flexManager);
 			break;
 		case PowerFlowAnalysis:
-			HolegGateway.solve(context, minimumNetwork, Iteration, flexManager, this);
+			HolegGateway.solve(settings, minimumNetwork, Iteration, flexManager, this);
 			break;
 		}
 	}

+ 30 - 47
src/ui/view/GroupNodeCanvas.java

@@ -82,6 +82,7 @@ public class GroupNodeCanvas extends AbstractCanvas implements MouseListener, Mo
             }
         }
 
+        showConnectionInformation = true;
         showedInformation[0] = true;
         showedInformation[1] = true;
         showedInformation[4] = true;
@@ -438,53 +439,32 @@ public class GroupNodeCanvas extends AbstractCanvas implements MouseListener, Mo
 		return percentages;
 	}
 	private void paintExitCable(Graphics2D g, ExitCable eCable) {
-		if(eCable.getStart() == null || eCable.getFinish() == null) {
-			System.out.println("Null wrong exitcable");
-			System.out.println(" wrongCable: " + " start:"  +eCable.getStart() + " end:"  +eCable.getFinish() + " state:" + eCable.getState());
-			return;
-		}
-		Position start = eCable.getStart().getPosition();
-		Position end = eCable.getFinish().getPosition();
-		float currentEnergy = eCable.getCable().getFlowEnergy();
-		float capacity = eCable.getCable().getModel().getCapacity();
-		boolean unlimited = eCable.getCable().getModel().isUnlimitedCapacity();
-		switch(eCable.getCable().getState()) {
-		case Burned:
-			g.setColor(Color.RED);
-			g.setStroke(new BasicStroke(2));
-			break;
-		case Working:
-			g.setColor(new Color(13, 175, 28));
-			g.setStroke(new BasicStroke(unlimited?2f:(currentEnergy / capacity * 2f) + 1));
-			break;
-		}
-		switch(eCable.getState()) {
-		case DOWN:
-		case DOWNDOWN:
-			g.drawLine(start.x, start.y, end.x, end.y);
-			Position middle = new Position((start.x + end.x) / 2, (start.y + end.y) / 2);
-			g.setFont(new Font("TimesRoman", Font.PLAIN, Math.max((int) (controller.getScale() / 3.5f), 10) )); 
-			g.drawString(currentEnergy + "/" + (unlimited?"\u221E":capacity) , middle.x, middle.y);
-			break;
-		case DOWNUP:
-		case UP:
-			Vector2d vStart = new Vector2d(start.x, start.y);
-			Vector2d vEnd = new Vector2d(end.x, end.y);
-			float stretchFactor = 4 * model.getScale();
-			stretchFactor =  (stretchFactor > vStart.getDistance(vEnd))? vStart.getDistance(vEnd) : stretchFactor;
-			Vector2d vPosition = vStart.add((vEnd.subtract(vStart)).normalize().multiply(stretchFactor));
-			Position result = new Position(Math.round(vPosition.getX()),Math.round(vPosition.getY()));
-			g.drawLine(start.x, start.y, result.x, result.y);
-			Position middle1 = new Position((start.x +result.x) / 2, (start.y + +result.y) / 2);
-			g.setFont(new Font("TimesRoman", Font.PLAIN, Math.max((int) (controller.getScale() / 3.5f), 10) )); 
-			g.drawString(currentEnergy + "/" + (unlimited?"\u221E":capacity) , middle1.x, middle1.y);
-			drawCanvasObject(g, "/Images/arrowUp.png" , result);
-			break;
-		default:
-			System.out.println("Error");
-			break;
-		}
-	}
+        if (eCable.getStart() == null || eCable.getFinish() == null) {
+            System.out.println("Null wrong exitcable");
+            System.out.println(" wrongCable: " + " start:" + eCable.getStart() + " end:" + eCable.getFinish() + " state:" + eCable.getState());
+            return;
+        }
+        Position start = eCable.getStart().getPosition();
+        Position end = eCable.getFinish().getPosition();
+
+        switch (eCable.getState()) {
+            case DOWNUP:
+            case UP:
+                Vector2d vStart = start.toVector();
+                Vector2d vEnd = end.toVector();
+                float stretchFactor = Math.min(4 * model.getScale(),  vStart.getDistance(vEnd));
+                Vector2d vPosition = vStart.add(vEnd.subtract(vStart).normalize().multiply(stretchFactor));
+
+                Position newEnd = new Position(vPosition);
+                MyCanvas.paintExitCable(controller, g, start, newEnd, eCable, showConnectionInformation);
+                drawCanvasObject(g, "/Images/arrowUp.png", newEnd);
+                break;
+            default:
+                MyCanvas.paintExitCable(controller, g, eCable, showConnectionInformation);
+                break;
+        }
+    }
+
 	private void paintSupplyBar(Graphics2D g, float percentage, Color color, Position pos) {
 		// +1, -2, -1 little Adjustment for pixel perfect alignment
 		int barWidth = (int) (controller.getScale());
@@ -560,6 +540,9 @@ public class GroupNodeCanvas extends AbstractCanvas implements MouseListener, Mo
 
 		
 		DecoratedGroupNode  actualGroupNode = controller.getSimManager().getActualVisualRepresentationalState().getCreatedGroupNodes().get(upperNode);
+		if (actualGroupNode == null)
+		    return;
+
 		//VisualState Representation:
 		for(ExitCable cable : actualGroupNode.getExitCableList()) {
 			paintExitCable(g2d, cable);

+ 63 - 65
src/ui/view/MyCanvas.java

@@ -4,7 +4,9 @@ import classes.*;
 
 import com.google.gson.JsonParseException;
 
+import holeg.HolegGateway;
 import holeg.HolegPowerFlowContext;
+import javafx.geometry.Pos;
 import ui.controller.Control;
 import ui.controller.SingletonControl;
 import ui.controller.UpdateController;
@@ -41,6 +43,7 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 /**
@@ -334,13 +337,6 @@ public class MyCanvas extends AbstractCanvas implements MouseListener,
 		this.addMouseListener(this);
 		this.addMouseMotionListener(this);
 	}
-
-	/**
-	 * Paints all Components on the Canvas.
-	 *
-	 * @param g
-	 *            Graphics
-	 */
 	
 	private Color getStateColor(HolonObjectState state) {
 		switch(state) {
@@ -398,7 +394,7 @@ public class MyCanvas extends AbstractCanvas implements MouseListener,
 				pos.y - controller.getScaleDiv2(),
 				controller.getScale(), controller.getScale() , null);
 	}
-	
+
 	public static void paintCable(Control controller, Graphics2D g, DecoratedCable cable, boolean isSelected, boolean showConnectionInformation) {
 		// Find cable start/end
 		AbstractCanvasObject a = cable.getModel().getA();
@@ -406,6 +402,10 @@ public class MyCanvas extends AbstractCanvas implements MouseListener,
 		Position start = a.getPosition();
 		Position end = b.getPosition();
 
+		paintCable(controller, g, a, b, start, end, cable, isSelected, showConnectionInformation);
+	}
+	
+	public static void paintCable(Control controller, Graphics2D g, AbstractCanvasObject a, AbstractCanvasObject b, Position start, Position end, DecoratedCable cable, boolean isSelected, boolean showConnectionInformation) {
 		// Find cable state
 		float currentEnergy = Math.abs(cable.getFlowEnergy());
 		float capacity = cable.getModel().getCapacity();
@@ -420,9 +420,8 @@ public class MyCanvas extends AbstractCanvas implements MouseListener,
 				g.setStroke(new BasicStroke(unlimited ? 2f : (Math.min(currentEnergy / capacity, 1) * 2f) + 1));
 				break;
 		}
-		if (isSelected) {
+		if (isSelected)
 			g.setColor(Color.lightGray);
-		}
 
 		// Check if we should draw arrow or line
 		if (cable.getFlowEnergy() > 2)
@@ -466,27 +465,19 @@ public class MyCanvas extends AbstractCanvas implements MouseListener,
 		drawCanvasObject(g, dSwitch.getState() == SwitchState.Open ? HolonSwitch.getSwitchOpenImage(): HolonSwitch.getSwitchClosedImage() , dSwitch.getModel().getPosition());
 	}
 	
-	private void paintExitCable(Graphics2D g, ExitCable eCable) {
-		Position start = eCable.getStart().getPosition();
-		Position end = eCable.getFinish().getPosition();
-		float currentEnergy = eCable.getCable().getFlowEnergy();
-		float capacity = eCable.getCable().getModel().getCapacity();
-		boolean unlimited = eCable.getCable().getModel().isUnlimitedCapacity();
-		switch(eCable.getCable().getState()) {
-		case Burned:
-			g.setColor(Color.RED);
-			g.setStroke(new BasicStroke(2));
-			break;
-		case Working:
-			g.setColor(new Color(13, 175, 28));
-			g.setStroke(new BasicStroke(unlimited?2f:(currentEnergy / capacity * 2f) + 1));
-			break;
-		}
-		g.drawLine(start.x, start.y, end.x, end.y);
-		Position middle = new Position((start.x + end.x) / 2, (start.y + end.y) / 2);
-		g.setFont(new Font("TimesRoman", Font.PLAIN, Math.max((int) (controller.getScale() / 3.5f), 10) )); 
-		g.drawString(currentEnergy + "/" + (unlimited?"\u221E":capacity) , middle.x, middle.y);
+	public static void paintExitCable(Control controller, Graphics2D g, ExitCable eCable, boolean showConnectionInformation) {
+		AbstractCanvasObject a = eCable.getStart();
+		AbstractCanvasObject b = eCable.getFinish();
+		paintExitCable(controller, g, a.getPosition(), b.getPosition(), eCable, showConnectionInformation);
 	}
+
+	public static void paintExitCable(Control controller, Graphics2D g, Position start, Position end, ExitCable eCable, boolean showConnectionInformation) {
+		AbstractCanvasObject a = eCable.getStart();
+		AbstractCanvasObject b = eCable.getFinish();
+		// End and start are reversed
+		paintCable(controller, g, a, b, end, start, eCable.getCable(), false, showConnectionInformation);
+	}
+
 	private void paintGroupNode(Graphics2D g, DecoratedGroupNode dGroupNode) {
 		Position pos = dGroupNode.getModel().getPosition();
 		g.setColor(Color.lightGray);
@@ -581,7 +572,7 @@ public class MyCanvas extends AbstractCanvas implements MouseListener,
 			g.fillRect(sx, y, x - sx, sy - y);
 		}
 	}
-	public void paintComponent(Graphics g) {		
+	public void paintComponent(Graphics g) {
 		super.paintComponent(g);
 		Graphics2D g2d = (Graphics2D) g;
 		g2d.setRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING,
@@ -595,43 +586,43 @@ public class MyCanvas extends AbstractCanvas implements MouseListener,
 		//<--
 		//SelectedCable
 		HashSet<Edge> selectedEdges = new HashSet<Edge>();
-		for(AbstractCanvasObject aCps:  model.getSelectedCpsObjects()) {
-			for(Edge edge: aCps.getConnections()) {
+		for (AbstractCanvasObject aCps : model.getSelectedCpsObjects()) {
+			for (Edge edge : aCps.getConnections()) {
 				selectedEdges.add(edge);
 			}
 		}
-		if(model.getSelectedEdge() != null) selectedEdges.add(model.getSelectedEdge());
+		if (model.getSelectedEdge() != null) selectedEdges.add(model.getSelectedEdge());
 		//timstep:
-		g.setFont(new Font("TimesNewRoman", Font.PLAIN, Math.max((int) (controller.getScale() / 3.5f), 10) )); 
+		g.setFont(new Font("TimesNewRoman", Font.PLAIN, Math.max((int) (controller.getScale() / 3.5f), 10)));
 		g2d.setColor(Color.BLACK);
-		
-		VisualRepresentationalState  visualState = controller.getSimManager().getActualVisualRepresentationalState();
+
+		VisualRepresentationalState visualState = controller.getSimManager().getActualVisualRepresentationalState();
 		//VisualState Representation:
-		for(ExitCable cable : visualState.getExitCableList()) {
-			paintExitCable(g2d, cable);
+		for (ExitCable cable : visualState.getExitCableList()) {
+			paintExitCable(controller, g2d, cable, showConnectionInformation);
 		}
-		for(DecoratedCable cable : visualState.getCableList()) {
+		for (DecoratedCable cable : visualState.getCableList()) {
 			paintCable(controller, g2d, cable, selectedEdges.contains(cable.getModel()), showConnectionInformation);
 		}
-		for(DecoratedGroupNode dGroupNode : visualState.getGroupNodeList()) {
+		for (DecoratedGroupNode dGroupNode : visualState.getGroupNodeList()) {
 			paintGroupNode(g2d, dGroupNode);
 		}
-		for(Consumer con: visualState.getConsumerList()) {
-			paintConsumer(g2d, con);					
+		for (Consumer con : visualState.getConsumerList()) {
+			paintConsumer(g2d, con);
 		}
-		for(Supplier sup: visualState.getSupplierList()) {
-			paintSupplier(g2d, sup);				
+		for (Supplier sup : visualState.getSupplierList()) {
+			paintSupplier(g2d, sup);
 		}
-		for(Passiv pas: visualState.getPassivList()) {
+		for (Passiv pas : visualState.getPassivList()) {
 			paintCanvasObject(g2d, pas);
 		}
-		for(DecoratedSwitch dSwitch : visualState.getSwitchList()) {
+		for (DecoratedSwitch dSwitch : visualState.getSwitchList()) {
 			paintSwitch(g2d, dSwitch);
 		}
-		for(Node node : visualState.getNodeList()) {
-			drawCanvasObject(g2d, "/Images/node.png" , node.getPosition());
+		for (Node node : visualState.getNodeList()) {
+			drawCanvasObject(g2d, "/Images/node.png", node.getPosition());
 		}
-		
+
 		//-->oldCode 
 		if (doMark) {
 			g2d.setColor(Color.BLACK);
@@ -643,30 +634,29 @@ public class MyCanvas extends AbstractCanvas implements MouseListener,
 		g2d.setColor(Color.BLUE);
 		g2d.setStroke(new BasicStroke(1));
 		Color transparentGrey = new Color(128, 174, 247, 40);
-		for(AbstractCanvasObject aCps:  model.getSelectedCpsObjects()) {
-			if(aCps instanceof Node) {
+		for (AbstractCanvasObject aCps : model.getSelectedCpsObjects()) {
+			if (aCps instanceof Node) {
 				Position pos = aCps.getPosition();
 				g2d.setColor(transparentGrey);
-				g2d.fillOval(pos.x - (int) (controller.getScaleDiv2()), pos.y - (int) (controller.getScaleDiv2()),  controller.getScale(),  controller.getScale());
+				g2d.fillOval(pos.x - (int) (controller.getScaleDiv2()), pos.y - (int) (controller.getScaleDiv2()), controller.getScale(), controller.getScale());
 				g2d.setColor(Color.LIGHT_GRAY);
 				g2d.setStroke(new BasicStroke(2));
-				g2d.drawOval(pos.x - (int) (controller.getScaleDiv2()), pos.y - (int) (controller.getScaleDiv2()),  controller.getScale(),  controller.getScale());
-			}
-			else {
+				g2d.drawOval(pos.x - (int) (controller.getScaleDiv2()), pos.y - (int) (controller.getScaleDiv2()), controller.getScale(), controller.getScale());
+			} else {
 				Position pos = aCps.getPosition();
 				g2d.setColor(transparentGrey);
-				g2d.fillRect(pos.x - (int) (controller.getScaleDiv2()* 1.5f), pos.y - (int) (controller.getScaleDiv2()* 1.5f), (int) (controller.getScale()* 1.5f) , (int) (controller.getScale()* 1.5f));
+				g2d.fillRect(pos.x - (int) (controller.getScaleDiv2() * 1.5f), pos.y - (int) (controller.getScaleDiv2() * 1.5f), (int) (controller.getScale() * 1.5f), (int) (controller.getScale() * 1.5f));
 				g2d.setColor(Color.LIGHT_GRAY);
 				g2d.setStroke(new BasicStroke(2));
-				g2d.drawRect(pos.x - (int) (controller.getScaleDiv2()* 1.5f), pos.y - (int) (controller.getScaleDiv2()* 1.5f), (int) (controller.getScale()* 1.5f) , (int) (controller.getScale()* 1.5f));				
+				g2d.drawRect(pos.x - (int) (controller.getScaleDiv2() * 1.5f), pos.y - (int) (controller.getScaleDiv2() * 1.5f), (int) (controller.getScale() * 1.5f), (int) (controller.getScale() * 1.5f));
 			}
 
 		}
 		//maybeReplace:
-		if(mayBeReplaced != null){
+		if (mayBeReplaced != null) {
 			g2d.setColor(Color.RED);
 			Position pos = mayBeReplaced.getPosition();
-			g2d.drawImage(ImageImport.loadImage("/Images/replace.png") , 
+			g2d.drawImage(ImageImport.loadImage("/Images/replace.png"),
 					pos.x + controller.getScaleDiv2(),
 					pos.y - controller.getScale(),
 					controller.getScaleDiv2(), controller.getScaleDiv2(), null);
@@ -674,13 +664,21 @@ public class MyCanvas extends AbstractCanvas implements MouseListener,
 		//<-- OldCode
 
 		// Draw solver job
-		HolegPowerFlowContext context = SingletonControl.getInstance().getControl().getSimManager().getHolegPowerFlowContext();
-		if (context != null) {
+		Set<HolegPowerFlowContext> contextSet = HolegGateway.getALlContext();
+		if (contextSet.size() > 0) {
+			float maxTime = 0;
+			int runningCount = 0;
+			for (HolegPowerFlowContext context : contextSet) {
+				maxTime = Math.max(maxTime, context.solverTimeMilliseconds);
+				if (context.isSolving())
+					runningCount++;
+			}
+
 			g2d.setColor(Color.BLACK);
-			if (context.isSolving())
-				g2d.drawString("Solver running...", 4, 14);
+			if (runningCount > 0)
+				g2d.drawString(String.format("%d of %d solver running...", runningCount, contextSet.size()), 4, 14);
 			else
-				g2d.drawString(String.format("Solver finished in %.2f ms", context.solverTimeMilliseconds), 4, 14);
+				g2d.drawString(String.format("Solver finished in max %.2f ms", maxTime), 4, 14);
 		}
 	}