Browse Source

Add multi threading for the sub grid solving

Henrik Kunzelmann 3 years ago
parent
commit
08883ec5e5

+ 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.
 	 * 

+ 58 - 21
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) {
@@ -93,7 +94,57 @@ public class HolegGateway {
         }
     }
 
-    public static synchronized 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)
@@ -101,7 +152,7 @@ public class HolegGateway {
         if (network == null)
             throw new IllegalArgumentException("network is null");
 
-        // This is used when doing async updates and is the grid we should show
+        // If we should show this solved grid
         if (context.showGridForVisual != null) {
             decorateNetwork(minimumNetwork, iteration, flexManager, network, context.showGridForVisual);
             context.showGridForVisual = null;
@@ -111,12 +162,8 @@ public class HolegGateway {
         boolean solve = !PowerFlowAnalysisMenu.getInstance().areUpdatesDisabled();
         Grid grid = convert(minimumNetwork, iteration, flexManager);
 
-        // Create settings when null
-        if (context.settings == null)
-            context.settings = PowerFlowSettings.getDefault();
-
         // Check if the grid is equal to one already solved
-        if (context.settings.onlyUpdateGridWhenChanged) {
+        if (settings.onlyUpdateGridWhenChanged) {
             for (Grid lastSolved : context.lastSolvedGrids) {
                 if (GridComparator.isEqual(lastSolved, grid)) {
                     decorateNetwork(minimumNetwork, iteration, flexManager, network, lastSolved);
@@ -136,17 +183,7 @@ public class HolegGateway {
         }
 
         // Stop old solver
-        if (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(() -> {
@@ -156,7 +193,7 @@ 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
@@ -175,7 +212,7 @@ public class HolegGateway {
 
         // Wait or save solver job
         try {
-            if (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

+ 14 - 1
src/holeg/HolegPowerFlowContext.java

@@ -6,7 +6,6 @@ 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;
@@ -19,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;
 		}
 	}

+ 3 - 0
src/ui/view/GroupNodeCanvas.java

@@ -540,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);

+ 43 - 34
src/ui/view/MyCanvas.java

@@ -4,6 +4,7 @@ import classes.*;
 
 import com.google.gson.JsonParseException;
 
+import holeg.HolegGateway;
 import holeg.HolegPowerFlowContext;
 import javafx.geometry.Pos;
 import ui.controller.Control;
@@ -42,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;
 
 /**
@@ -570,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,
@@ -584,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()) {
+		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);
@@ -632,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);
@@ -663,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);
 		}
 	}