ソースを参照

major changes to UI
- updated holon object rendering
- updated line rendering
- optimized slack node placement
- manual placement of slack nodes
- change line length in kilometers

Henrik Kunzelmann 3 年 前
コミット
2640fa2943

BIN
res/Button_Images/Thumbs.db


BIN
res/Images/slack-node.png


+ 11 - 4
src/classes/Edge.java

@@ -17,6 +17,10 @@ public class Edge {
     // Working would be false
     @Expose
     private float maxCapacity;
+    @Expose
+    private float realLength = 1;
+    @Expose
+    private float override = 1;
     ArrayList<Integer> tags;
     // for internal use --> flow of electricity (simulation state)
     ArrayList<Integer> pseudoTags;
@@ -27,8 +31,6 @@ public class Edge {
     
     
     
-    
-    
     /**
      * Getter for the length of the Cable.
      * Always calculate never saved.
@@ -39,6 +41,10 @@ public class Edge {
     	return (float)a.getPosition().Distance(b.getPosition());
     }
 
+    public float getRealLength() {
+        return realLength;
+    }
+
     @Expose
     private boolean breakedManuel = false;
     @Expose
@@ -97,8 +103,9 @@ public class Edge {
     }
 
 
-
-
+    public void setRealLength(float realLength) {
+        this.realLength = realLength;
+    }
 
     /**
      * Getter for the Source.

+ 17 - 8
src/holeg/HolegGateway.java

@@ -2,11 +2,12 @@ package holeg;
 
 import classes.AbstractCanvasObject;
 import classes.HolonObject;
+import classes.Node;
 import holeg.model.Grid;
 import holeg.model.GridEdge;
 import holeg.model.GridNode;
+import holeg.model.NodeType;
 import holeg.power_flow.ComplexNumber;
-import holeg.power_flow.SolverSettings;
 import holeg.simple_grid.SimpleGridBuilder;
 import holeg.simple_grid.SimpleGridEdge;
 import holeg.simple_grid.SimpleGridNode;
@@ -29,6 +30,13 @@ public class HolegGateway {
             else
                 node = gridBuilder.addGenerator(new ComplexNumber(power, 0));
             node.tag = object;
+
+            if (object.getElements().stream().anyMatch(e -> e.getEleName().equalsIgnoreCase("SLACK")))
+                node.setType(NodeType.Slack);
+            else if (object.getElements().stream().anyMatch(e -> e.getEleName().equalsIgnoreCase("TRANSFORMER")))
+                node.setType(NodeType.Transformer);
+            else
+                node.setType(NodeType.Bus);
             canvasToGrid.put(object, node);
         }
         for (AbstractCanvasObject object : network.getNodeAndSwitches()) {
@@ -41,7 +49,7 @@ public class HolegGateway {
             SimpleGridNode b = canvasToGrid.get(cable.getModel().getB());
             if (a == null || b == null)
                 continue;
-            SimpleGridEdge edge = gridBuilder.connect(a, b, 1);
+            SimpleGridEdge edge = gridBuilder.connect(a, b, cable.getModel().getRealLength());
             edge.tag = cable;
         }
         return gridBuilder.getGrid();
@@ -51,10 +59,10 @@ public class HolegGateway {
         for (GridNode node : grid.getNodes()) {
             SimpleGridNode simpleNode = (SimpleGridNode) node;
             if (node.getPowerGeneration().lenSquared() > 0)
-                decoratedNetwork.getSupplierList().add(new Supplier((HolonObject) simpleNode.tag, (float) node.getPowerGeneration().real, 0));
-            else if (node.getPowerConsumption().lenSquared() < 0)
-                decoratedNetwork.getConsumerList().add(new Consumer((HolonObject) simpleNode.tag));
-            else
+                decoratedNetwork.getSupplierList().add(new Supplier((HolonObject) simpleNode.tag, (float) node.getPowerGeneration().real, 0, simpleNode.voltage, simpleNode.phaseDegrees, simpleNode.type == NodeType.Slack));
+            else if (node.getPowerConsumption().lenSquared() > 0)
+                decoratedNetwork.getConsumerList().add(new Consumer((HolonObject) simpleNode.tag, (float) node.getPowerConsumption().real, simpleNode.voltage, simpleNode.phaseDegrees, Math.cos(simpleNode.getPowerConsumption().angle()), simpleNode.type == NodeType.Slack));
+            else if (simpleNode.tag instanceof HolonObject)
                 decoratedNetwork.getPassivNoEnergyList().add(new Passiv((HolonObject) simpleNode.tag));
         }
         for (GridEdge edge : grid.getEdges()) {
@@ -63,7 +71,8 @@ public class HolegGateway {
                     new DecoratedCable(
                             ((IntermediateCableWithState)simpleGridEdge.tag).getModel(),
                             DecoratedCable.CableState.Working,
-                            (float)simpleGridEdge.power.real));
+                            (float)simpleGridEdge.power.real,
+                            (float)simpleGridEdge.loss.real));
         }
     }
 
@@ -71,7 +80,7 @@ public class HolegGateway {
         Grid grid = convert(minimumNetwork, iteration, flexManager);
 
         HolegPowerFlow powerFlow = new HolegPowerFlow();
-        GridSolverResult result = powerFlow.solve(grid);
+        GridSolverResult result = powerFlow.solve(grid, PowerFlowSettings.getDefault());
         decorateNetwork(minimumNetwork, iteration, flexManager, network, grid);
 
         if (PowerFlowAnalysisMenu.getInstance().shouldShowResult())

+ 232 - 83
src/holeg/HolegPowerFlow.java

@@ -3,6 +3,7 @@ package holeg;
 import holeg.model.Grid;
 import holeg.model.GridEdge;
 import holeg.model.GridNode;
+import holeg.model.NodeType;
 import holeg.power_flow.*;
 import holeg.ui.PowerFlowAnalysisMenu;
 
@@ -16,42 +17,100 @@ import java.util.*;
 public class HolegPowerFlow {
     private Random random = new Random();
 
-    public GridSolverResult solve(Grid grid) {
-        List<Grid> islands = findIslands(grid);
+    public GridSolverResult solve(Grid grid, PowerFlowSettings settings) {
+        if (grid == null)
+            throw new IllegalArgumentException("grid is null");
+        if (settings == null)
+            throw new IllegalArgumentException("settings is null");
 
-        // Use default settings
-        SolverSettings settings = new SolverSettings();
+        List<Grid> islands = findIslands(grid);
 
         boolean anySolved = false;
         GridSolverResult sumResult = GridSolverResult.Solved;
+        double minSlackPower = Double.MAX_VALUE;
 
         // Try to solve each island
         for (Grid island : islands) {
             boolean solved = false;
 
-            // Try to solve problem 6 times -> each time the slack node has a new position (random)
-            for (int i = 0; i < 6; i++) {
+            // Calculate number of calculation tries
+            int solveTryCount;
+            if (island.getNodes().stream().anyMatch(n -> n.getType() == NodeType.Slack))
+                solveTryCount = 1;
+            else if (settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.MinimizeSlack)
+                solveTryCount = island.getNodes().size();
+            else if (settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.RandomNode
+                    || settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.RandomProducer)
+                solveTryCount = island.getNodes().size() / 2;
+            else
+                solveTryCount = 1;
+
+            PowerFlowProblem bestProblem = null;
+            SolverResult bestResult = null;
+
+            for (int i = 0; i < solveTryCount; i++) {
                 // Build problem and solve it
-                PowerFlowProblem problem = buildProblem(island);
+                PowerFlowProblem problem = buildProblem(island, i, settings);
+
+                // See if we can skip the problem
+                if (problem.buses.length <= 1) {
+                    solved = true;
+                    break;
+                }
+                if (settings.skipGridsWithNoProducers && problem.getProducers().isEmpty()) {
+                    solved = true;
+                    break;
+                }
+
                 Solver solver = new NewtonRaphsonSolver();
-                SolverResult result = solver.solve(problem, settings);
+                SolverResult result = solver.solve(problem, settings.solverSettings);
 
                 // Debug message
-                if (PowerFlowAnalysisMenu.getInstance().shouldShowDebug() && (result.solved || i == 3))
+                if (PowerFlowAnalysisMenu.getInstance().shouldShowDebug() && (result.solved || i == solveTryCount - 1))
                     debugShowResultAsMessageBox(problem, result);
 
-                // Update grid if solved
                 if (result.solved) {
-                    updateGrid(island, problem, result);
                     solved = true;
-                    break;
+
+                    // Simply use the first solved result
+                    if (settings.slackNodePlacementStrategy != SlackNodePlacementStrategy.MinimizeSlack) {
+                        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;
+                    }
                 }
             }
 
-            if (!solved)
-                sumResult = GridSolverResult.Error;
+            // Update grid if solved
+            if (solved) {
+                // Check limits
+                boolean error = false;
+                if (bestResult != null) {
+                    if (bestResult.flowData.busInjection[0].len() > settings.maxSlackPowerUntilInvalid)
+                        error = true;
+                    if (Arrays.stream(bestResult.voltages).anyMatch(u -> u.real < settings.minVoltageUntilInvalid))
+                        error = true;
+                }
+
+                // Update grid or set error
+                if (!error) {
+                    if (bestProblem != null)
+                        updateGrid(island, bestProblem, bestResult);
+                    anySolved = true;
+                }
+                else
+                    sumResult = GridSolverResult.Error;
+            }
             else
-                anySolved = true;
+                sumResult = GridSolverResult.Error;
         }
 
         // Check if some islands failed, but not all
@@ -108,7 +167,7 @@ public class HolegPowerFlow {
         return islands;
     }
 
-    private PowerFlowProblem buildProblem(Grid grid) {
+    private PowerFlowProblem buildProblem(Grid grid, int tryCount, PowerFlowSettings settings) {
         List<Bus> buses = new ArrayList<>();
         List<Line> lines = new ArrayList<>();
 
@@ -119,11 +178,11 @@ public class HolegPowerFlow {
             createGridLine(grid, lines, edge);
 
         // Scale voltages and scale power
-        double scaleVoltage = scaleVoltages(buses);
+        double scaleVoltage = 1.0 / 230.0;
         double scalePower = scalePower(buses);
 
         // Find position for slack node
-        addSlackNode(buses, lines);
+        addSlackNode(buses, lines, tryCount, settings);
 
         // Sort lines
         lines.sort(Comparator.comparingInt(a -> a.from));
@@ -136,20 +195,24 @@ public class HolegPowerFlow {
         Bus bus = new Bus();
         bus.delta = 0;
         bus.voltage = 1;
-        bus.Pl = node.getPowerConsumption().real;
-        bus.Ql = node.getPowerConsumption().imaginary;
+        bus.Pl = Math.abs(node.getPowerConsumption().real);
+        bus.Ql = Math.abs(node.getPowerConsumption().imaginary);
         bus.tag = node;
 
-        if (node.getPowerGeneration().isZero()) {
+        if (node.getType() == NodeType.Slack)
+            bus.type = BusType.Slack;
+        else if (node.getPowerGeneration().isZero()) {
             bus.Pg = 0;
             bus.Qg = 0;
             bus.type = BusType.PQ;
         }
         else {
-            bus.Pg = node.getPowerGeneration().real;
-            bus.Qg = node.getPowerGeneration().imaginary;
+            bus.Pg = Math.abs(node.getPowerGeneration().real);
+            bus.Qg = Math.abs(node.getPowerGeneration().imaginary);
             bus.type = BusType.PV;
         }
+        bus.Qmin = -1000000;
+        bus.Qmax = 1000000;
 
         buses.add(bus);
     }
@@ -158,32 +221,19 @@ public class HolegPowerFlow {
         Line line = new Line();
         line.tag = edge;
         // Calculate values for line based on sample datasheet
-        line.R = 0.01938; //0.791 * edge.getLengthKilometers();
-        line.X = 0.1; //0.11 * edge.getLengthKilometers();
-        line.B_2 = 0.1; //0.5 * 0.350141 * edge.getLengthKilometers();
+        line.R = 0.000194 * edge.getLengthKilometers();
+        line.X = 0.000592 * edge.getLengthKilometers();
+        line.B_2 = 0.000528;
         line.a = 1;
-        line.from = grid.getNodes().indexOf(edge.getFrom());
-        line.to = grid.getNodes().indexOf(edge.getTo());
-        lines.add(line);
-    }
 
-    private double scaleVoltages(List<Bus> buses) {
-        double decimal = 1;
-        while(true) {
-            boolean nextDecimal = false;
-            for (Bus bus : buses) {
-                if (bus.voltage > decimal) {
-                    nextDecimal = true;
-                    break;
-                }
-            }
-            if (!nextDecimal)
-                break;
-            decimal *= 10;
-        }
-        for (Bus bus : buses)
-            bus.voltage /= decimal;
-        return 1.0 / decimal;
+        // Calculate from/to, swap if needed
+        int from = grid.getNodes().indexOf(edge.getFrom());
+        int to = grid.getNodes().indexOf(edge.getTo());
+        line.reverse = from > to;
+        line.from = Math.min(from, to);
+        line.to = Math.max(from, to);
+
+        lines.add(line);
     }
 
     private double scalePower(List<Bus> buses) {
@@ -205,53 +255,148 @@ public class HolegPowerFlow {
             bus.Pg /= decimal;
             bus.Ql /= decimal;
             bus.Qg /= decimal;
+            bus.Qmin /= decimal;
+            bus.Qmax /= decimal;
         }
         return 1.0 / decimal;
     }
 
-    private void addSlackNode(List<Bus> buses, List<Line> lines) {
-        // Find possible positions
-        List<Integer> possiblePositions = new ArrayList<>();
-        for (int i = 0; i < buses.size(); i++)
-            if (buses.get(i).Pg > 0)
-                possiblePositions.add(i);
-
-        // No generators? Add all positions
-        if (possiblePositions.size() == 0) {
-            for (int i = 0; i < buses.size(); i++)
-                possiblePositions.add(i);
+    private void addSlackNode(List<Bus> buses, List<Line> lines, int tryCount, PowerFlowSettings settings) {
+        // Skip if have already slack node
+        int slackNodePosition = -1;
+        for (int i = 0; i < buses.size(); i++) {
+            if (buses.get(i).type == BusType.Slack) {
+                slackNodePosition = i;
+                break;
+            }
         }
 
-        // Choose position in grid
-        int position = possiblePositions.get(random.nextInt(possiblePositions.size()));
-
-        // Create slack node
-        Bus slack = new Bus();
-        slack.voltage = 1;
-        slack.delta = 0;
-        slack.type = BusType.Slack;
+        // Position
+        int position;
+        if (slackNodePosition >= 0)
+            position = slackNodePosition;
+        else if (settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.FirstNode)
+            position = 0;
+        else if (settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.RandomNode
+                || settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.RandomProducer) {
+
+            // Find possible positions
+            List<Integer> possiblePositions = new ArrayList<>();
+            if (settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.RandomProducer) {
+                for (int i = 0; i < buses.size(); i++)
+                    if (buses.get(i).isProducer())
+                        possiblePositions.add(i);
+            }
 
-        buses.add(slack);
+            // Choose position in grid
+            if (possiblePositions.size() == 0) // No generators or strategy not random producer? Add all positions
+                position = random.nextInt(buses.size());
+            else
+                position = possiblePositions.get(random.nextInt(possiblePositions.size()));
+        }
+        else if (settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.LargestProducer) {
+            position = 0;
+
+            // Find producer with max Pg value
+            double maxPower = -Double.MAX_VALUE;
+            for (int i = 0; i < buses.size(); i++) {
+                double power = buses.get(i).Pg;
+                if (power > maxPower) {
+                    position = i;
+                    maxPower = power;
+                }
+            }
+        }
+        else if (settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.LargestConsumer) {
+            position = 0;
+
+            // Find producer with max Pl value
+            double maxPower = -Double.MAX_VALUE;
+            for (int i = 0; i < buses.size(); i++) {
+                double power = buses.get(i).Pl;
+                if (power > maxPower) {
+                    position = i;
+                    maxPower = power;
+                }
+            }
+        }
+        else if (settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.MinimizeSlack) {
+            position = tryCount;
+            double power = buses.get(position).Pg + buses.get(position).Qg + buses.get(position).Pl + buses.get(position).Ql;
+            if (Math.abs(power) < 0.01)
+                return;
+        }
+        else
+            throw new UnsupportedOperationException("SlackNodePlacementStrategy not implemented");
+
+        // Already contains slack node or we should replace one node with a new slack node
+        if (slackNodePosition >= 0 || settings.replaceNodeWithSlackNode) {
+            Bus slack = buses.get(position);
+            slack.type = BusType.Slack;
+
+            if (position > 0) {
+                // Swap position with first element
+                Bus zeroNode = buses.get(0);
+                buses.set(0, slack);
+                buses.set(position, zeroNode);
+
+                // Fix indices of lines (we changed the order in the list)
+                for (Line line : lines) {
+                    if (line.from == position)
+                        line.from = 0;
+                    else if (line.from == 0)
+                        line.from = position;
+
+                    if (line.to == position)
+                        line.to = 0;
+                    else if (line.to == 0)
+                        line.to = position;
+
+                    // We need to swap from/to
+                    if (line.from > line.to) {
+                        int t = line.from;
+                        line.from = line.to;
+                        line.to = t;
+                        line.reverse = !line.reverse;
+                    }
+                }
+            }
+        }
+        else {
+            // Create slack node
+            Bus slack = new Bus();
+            slack.voltage = 1;
+            slack.delta = 0;
+            slack.type = BusType.Slack;
+
+            buses.add(0, slack);
+
+            // Fix line indices
+            for (Line line : lines) {
+                line.from += 1;
+                line.to += 1;
+            }
 
-        // Create short line
-        Line line = new Line();
-        line.from = position;
-        line.to = buses.indexOf(slack);
-        line.R = 0.01938;
-        line.X = 0.1;
-        line.B_2 = 0.1;
-        line.a = 1;
+            // Create short line
+            Line line = new Line();
+            line.from = position + 1;
+            line.to = buses.indexOf(slack);
+            line.R = 0.01938;
+            line.X = 0.1;
+            line.B_2 = 0.1;
+            line.a = 1;
 
-        lines.add(line);
+            lines.add(line);
+        }
     }
 
     private void debugShowResultAsMessageBox(PowerFlowProblem problem, SolverResult result) {
-        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+        try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
             final String utf8 = StandardCharsets.UTF_8.name();
-            try (PrintStream ps = new PrintStream(baos, true, utf8)) {
+            try (PrintStream ps = new PrintStream(stream, true, utf8)) {
                 result.printTo(problem, ps);
             }
-            String data = baos.toString(utf8);
+            String data = stream.toString(utf8);
             JOptionPane.showMessageDialog(null, data,"Debug output", JOptionPane.INFORMATION_MESSAGE);
         } catch (IOException e) {
             e.printStackTrace();
@@ -265,8 +410,11 @@ public class HolegPowerFlow {
             if (node == null)
                 continue;
 
-            node.setVoltage(result.voltages[i].real / problem.scaleVoltage);
+            node.setVoltage(result.voltages[i].len() / problem.scaleVoltage);
+            node.setPhase(Math.toDegrees(result.voltages[i].angle()));
             node.setCurrent(result.flowData.busCurrent[i] / currentScale);
+            if (problem.buses[i].type == BusType.Slack)
+                node.setType(NodeType.Slack);
         }
 
         for (int i = 0; i < problem.lines.length; i++) {
@@ -274,9 +422,10 @@ public class HolegPowerFlow {
             if (edge == null)
                 continue;
 
-            edge.setPowerFlow(result.flowData.linePower[i].multiply(1 / problem.scalePower));
-            edge.setLineLoss(result.flowData.linePowerLoss[i].multiply(1 / problem.scalePower));
-            edge.setCurrent(result.flowData.lineCurrent[i] / currentScale);
+            double reverseFactor = problem.lines[i].reverse ? -1 : 1;
+            edge.setPowerFlow(result.flowData.linePower[i].multiply(1 / problem.scalePower).multiply(reverseFactor));
+            edge.setLineLoss(result.flowData.linePowerLoss[i].multiply(1 / problem.scalePower).multiply(reverseFactor));
+            edge.setCurrent(result.flowData.lineCurrent[i] / currentScale * reverseFactor);
         }
     }
 }

+ 25 - 0
src/holeg/PowerFlowSettings.java

@@ -0,0 +1,25 @@
+package holeg;
+
+import holeg.power_flow.SolverSettings;
+
+public class PowerFlowSettings {
+    public SolverSettings solverSettings;
+    public boolean skipGridsWithNoProducers;
+    public boolean replaceNodeWithSlackNode;
+    public SlackNodePlacementStrategy slackNodePlacementStrategy;
+    public double maxSlackPowerUntilInvalid;
+    public double minVoltageUntilInvalid;
+
+    public PowerFlowSettings() {
+        solverSettings = new SolverSettings(); // use default
+        skipGridsWithNoProducers = true;
+        replaceNodeWithSlackNode = true;
+        slackNodePlacementStrategy = SlackNodePlacementStrategy.MinimizeSlack;
+        maxSlackPowerUntilInvalid = Double.MAX_VALUE;
+        minVoltageUntilInvalid = 0.3;
+    }
+
+    public static PowerFlowSettings getDefault() {
+        return new PowerFlowSettings();
+    }
+}

+ 10 - 0
src/holeg/SlackNodePlacementStrategy.java

@@ -0,0 +1,10 @@
+package holeg;
+
+public enum SlackNodePlacementStrategy {
+    FirstNode,
+    RandomNode,
+    RandomProducer,
+    LargestProducer,
+    LargestConsumer,
+    MinimizeSlack
+}

+ 3 - 0
src/holeg/model/GridNode.java

@@ -7,9 +7,12 @@ import java.util.List;
 public interface GridNode {
     List<GridEdge> getEdges();
 
+    NodeType getType();
     ComplexNumber getPowerConsumption();
     ComplexNumber getPowerGeneration();
 
     void setVoltage(double voltage);
+    void setPhase(double phaseDegrees);
     void setCurrent(double current);
+    void setType(NodeType type);
 }

+ 7 - 0
src/holeg/model/NodeType.java

@@ -0,0 +1,7 @@
+package holeg.model;
+
+public enum NodeType {
+    Bus,
+    Slack,
+    Transformer
+}

+ 4 - 0
src/holeg/power_flow/Bus.java

@@ -27,4 +27,8 @@ public class Bus {
         this.Qmax = qmax / 100;
         this.type = type;
     }
+
+    public boolean isProducer() {
+        return Pg > 0 || Qg > 0;
+    }
 }

+ 2 - 0
src/holeg/power_flow/Line.java

@@ -3,6 +3,7 @@ package holeg.power_flow;
 public class Line {
     public int from;
     public int to;
+    public boolean reverse;
 
     public double R;
     public double X;
@@ -18,6 +19,7 @@ public class Line {
     public Line(int from, int to, double r, double x, double b_2, double a) {
         this.from = from - 1;
         this.to = to - 1;
+        this.reverse = false;
         this.R = r;
         this.X = x;
         this.B_2 = b_2;

+ 0 - 1
src/holeg/power_flow/NewtonRaphsonSolver.java

@@ -3,7 +3,6 @@ package holeg.power_flow;
 import Jama.LUDecomposition;
 import Jama.Matrix;
 
-import java.io.PrintStream;
 import java.util.Arrays;
 
 public class NewtonRaphsonSolver implements Solver {

+ 17 - 10
src/holeg/power_flow/PowerFlowProblem.java

@@ -1,5 +1,8 @@
 package holeg.power_flow;
 
+import java.util.ArrayList;
+import java.util.List;
+
 public class PowerFlowProblem {
     public final Bus[] buses;
     public final Line[] lines;
@@ -17,6 +20,14 @@ public class PowerFlowProblem {
         this.scalePower = scalePower;
     }
 
+    public List<Integer> getProducers() {
+        List<Integer> producers = new ArrayList<>();
+        for (int i = 0; i < buses.length; i++)
+            if (buses[i].isProducer())
+                producers.add(i);
+        return producers;
+    }
+
     public boolean checkForValidData() {
         // Check per unit scale
         if (scaleVoltage <= 0)
@@ -25,7 +36,6 @@ public class PowerFlowProblem {
             return false;
 
         // Check buses
-        boolean slackBusFound = false;
         for (int i = 0; i < buses.length; i++) {
             Bus bus = buses[i];
             if (bus == null)
@@ -36,17 +46,12 @@ public class PowerFlowProblem {
                 return false;
 
             // Check for multiple slack buses
-            if (bus.type == BusType.Slack) {
-                if (slackBusFound)
-                    return false;
-                slackBusFound = true;
-            }
+            if (bus.type == BusType.Slack && i != 0)
+                return false;
+            if (bus.type != BusType.Slack && i == 0)
+                return false;
         }
 
-        // No slack bus
-        if (!slackBusFound)
-            return false;
-
         // Check lines
         for (int i = 0; i < lines.length; i++) {
             Line line = lines[i];
@@ -58,6 +63,8 @@ public class PowerFlowProblem {
                 return false;
             if (line.from < 0 || line.from >= buses.length)
                 return false;
+            if (line.from == line.to)
+                return false;
             // Invalid values for R and X
             if (line.R < 0 || line.X < 0)
                 return false;

+ 2 - 2
src/holeg/power_flow/SolverResult.java

@@ -54,7 +54,7 @@ public class SolverResult {
 
         // Buses
         for (int i = 0; i < voltages.length; i++) {
-            stream.printf("[Bus %2d %s] Voltage: %5.4f Vpu (%4.2f°), Power: %4.2f MW",
+            stream.printf("[Bus %2d %s] Voltage: %5.4f Vpu (%4.2f°), Power: %4.2f kW",
                     i,
                     problem.buses[i].type.name(),
                     voltages[i].len(),
@@ -66,7 +66,7 @@ public class SolverResult {
         // Lines
         for (int i = 0; i < problem.lines.length; i++) {
             Line line = problem.lines[i];
-            stream.printf("[Line %2d -> %2d] Power: %5.3f MW, %5.3f Mvar, Loss: %5.3f MW, %5.3f Mvar",
+            stream.printf("[Line %2d -> %2d] Power: %5.3f kW, %5.3f kvar, Loss: %5.3f kW, %5.3f kvar",
                     line.from,
                     line.to,
                     flowData.linePower[i].real / problem.scalePower,

+ 1 - 1
src/holeg/simple_grid/SimpleGridEdge.java

@@ -8,7 +8,7 @@ import java.io.PrintStream;
 public class SimpleGridEdge implements GridEdge {
     public SimpleGridNode from;
     public SimpleGridNode to;
-    public double lengthKilometers = 0;
+    public double lengthKilometers = 1;
     public double current = 0;
     public ComplexNumber power = ComplexNumber.Zero;
     public ComplexNumber loss = ComplexNumber.Zero;

+ 18 - 0
src/holeg/simple_grid/SimpleGridNode.java

@@ -2,6 +2,7 @@ package holeg.simple_grid;
 
 import holeg.model.GridEdge;
 import holeg.model.GridNode;
+import holeg.model.NodeType;
 import holeg.power_flow.ComplexNumber;
 
 import java.io.PrintStream;
@@ -12,8 +13,15 @@ public class SimpleGridNode implements GridNode {
     public ComplexNumber powerConsumption = ComplexNumber.Zero;
     public ComplexNumber powerGeneration = ComplexNumber.Zero;
     public double voltage;
+    public double phaseDegrees;
     public double current;
     public Object tag;
+    public NodeType type;
+
+    @Override
+    public NodeType getType() {
+        return type;
+    }
 
     @Override
     public List<GridEdge> getEdges() {
@@ -35,11 +43,21 @@ public class SimpleGridNode implements GridNode {
         this.voltage = voltage;
     }
 
+    @Override
+    public void setPhase(double phaseDegrees) {
+        this.phaseDegrees = phaseDegrees;
+    }
+
     @Override
     public void setCurrent(double current) {
         this.current = current;
     }
 
+    @Override
+    public void setType(NodeType type) {
+        this.type = type;
+    }
+
     public void print(PrintStream stream) {
         stream.printf("consumption: %s, generation: %s, voltage: %.3f V, current: %.3f A", powerConsumption.toString(), powerGeneration.toString(), voltage, current);
         stream.println();

+ 3 - 2
src/holeg/test_headless/TestHeadlessProgram.java

@@ -2,10 +2,11 @@ package holeg.test_headless;
 
 
 import holeg.HolegPowerFlow;
+import holeg.PowerFlowSettings;
 import holeg.model.Grid;
 import holeg.model.GridEdge;
 import holeg.model.GridNode;
-import holeg.power_flow.*;
+import holeg.power_flow.ComplexNumber;
 import holeg.simple_grid.SimpleGridBuilder;
 import holeg.simple_grid.SimpleGridEdge;
 import holeg.simple_grid.SimpleGridNode;
@@ -32,7 +33,7 @@ public class TestHeadlessProgram {
         // Grid
         Grid grid = builder.getGrid();
         HolegPowerFlow flow = new HolegPowerFlow();
-        flow.solve(grid);
+        flow.solve(grid, PowerFlowSettings.getDefault());
 
         // Show results
         for (GridNode node : grid.getNodes())

+ 0 - 1
src/holeg/ui/PowerFlowAnalysisMenu.java

@@ -1,6 +1,5 @@
 package holeg.ui;
 
-import ui.model.MinimumModel;
 import ui.model.Model;
 
 import javax.swing.*;

+ 4 - 0
src/ui/controller/CategoryController.java

@@ -44,11 +44,15 @@ public class CategoryController {
 		addNewCategory("Energy");
 		addNewCategory("Building");
 		addNewCategory("Component");
+		addNewCategory("Power Flow Analysis");
 		addNewHolonObject(mpC.searchCat("Energy"), "Power Plant", new ArrayList<HolonElement>(),
 				"/Images/power-plant.png");
 		addNewHolonObject(mpC.searchCat("Building"), "House", new ArrayList<HolonElement>(), "/Images/home-2.png");
 		addNewHolonSwitch(mpC.searchCat("Component"), "Switch", "/Images/switch-on.png");
 		addNewHolonBattery(mpC.searchCat("Component"), "Battery", "/Images/battery.png");
+
+		addNewHolonObject(mpC.searchCat("Power Flow Analysis"), "Slack", new ArrayList<HolonElement>(), "/Images/slack-node.png");
+		addNewHolonObject(mpC.searchCat("Power Flow Analysis"), "Transformer", new ArrayList<HolonElement>(), "/Images/transformer-1.png");
 	}
 
 	/**

+ 6 - 1
src/ui/controller/Control.java

@@ -521,7 +521,12 @@ public class Control {
      * @throws IOException
      */
     public void loadAutoSave(String path) throws IOException {
-        loadController.readJson(path);
+        try {
+            loadController.readJson(path);
+        }
+        catch(Exception e) {
+            e.printStackTrace();
+        }
     }
 
     public ArrayList<Integer> loadSavedWindowDimensionsIfExistent() {

+ 2 - 0
src/ui/controller/LoadController.java

@@ -247,6 +247,8 @@ public class LoadController {
     private void loadCategoryElements(JsonElement jsonElement, HashMap<Integer, HolonElement> eleDispatch) {
         HolonElement temp = model.getGson().fromJson(jsonElement.getAsJsonObject().get("properties").getAsJsonObject(),
                 HolonElement.class);
+        if (temp.getSaving() == null)
+            return;
         eleDispatch.put(temp.getId(), temp);
         initElements(temp);
         objC.addElementIntoCategoryObject(temp.getSaving().getKey(), temp.getSaving().getValue(), temp);

+ 2 - 0
src/ui/controller/ObjectController.java

@@ -38,6 +38,8 @@ public class ObjectController {
         addNewElementIntoCategoryObject("Building", "House", "PC", 3, -250);
         addNewElementIntoCategoryObject("Building", "House", "Light", 5, -50);
         addNewElementIntoCategoryObject("Building", "House", "Solar Panels", 1, 300);
+        addNewElementIntoCategoryObject("Power Flow Analysis", "Slack", "SLACK", 1, 0);
+        addNewElementIntoCategoryObject("Power Flow Analysis", "Transformer", "TRANSFORMER", 1, 0);
     }
 
     /**

+ 1 - 1
src/ui/controller/SimulationManager.java

@@ -143,7 +143,7 @@ public class SimulationManager {
 		ArrayList<DecoratedCable> leftOverDecoratedCables = new ArrayList<DecoratedCable>();
 		
 		for(IntermediateCableWithState cable: leftOver) {
-			leftOverDecoratedCables.add(new DecoratedCable(cable.getModel(), cable.getState(), 0.0f));
+			leftOverDecoratedCables.add(new DecoratedCable(cable.getModel(), cable.getState(), 0, 0));
 		}
 		ArrayList<DecoratedSwitch> listOfDecoratedSwitches = decorateSwitches(minimumModel, timestep);
 		DecoratedState stateFromThisTimestep = new DecoratedState(decorNetworks, leftOverDecoratedCables, listOfDecoratedSwitches, newFlexManager, timestep);

+ 3 - 1
src/ui/controller/UpdateController.java

@@ -450,11 +450,13 @@ public class UpdateController {
 			// Status displayed
             Object[] tempStatus = {"Length", model.getSelectedEdge().getLength() };
             model.getPropertyTable().addRow(tempStatus);
+			model.getPropertyTable().addRow(new Object[]{"Real length in km", model.getSelectedEdge().getRealLength()});
             // For edges, the only possible editable cell is the max
 			// flow
 			model.getPropertyTable().setCellEditable(0, 1, false);
 			model.getPropertyTable().setCellEditable(2, 1, true);
-			model.getPropertyTable().setCellEditable(3, 1, true);
+			model.getPropertyTable().setCellEditable(3, 1, false);
+			model.getPropertyTable().setCellEditable(4, 1, true);
 		} else if (getActualCps() == null) {
 			deleteRows(model.getSingleTable());
 			deleteRows(model.getMultiTable());

+ 16 - 2
src/ui/model/Consumer.java

@@ -12,9 +12,16 @@ public class Consumer extends DecoratedHolonObject {
 	private float energyNeededFromNetwork;
 	private float energyFromConsumingElemnets;
 	private float energySelfSupplied;
-	public Consumer(HolonObject objectToLookAt) {
+	private double powerFactor;
+	private boolean isSlack;
+
+	public Consumer(HolonObject objectToLookAt, float energyNeededFromNetwork, double voltage, double phaseDegrees, double powerFactor, boolean isSlack) {
 		super(objectToLookAt);
-		energyNeededFromNetwork = 0.0f;
+		this.energyNeededFromNetwork = energyNeededFromNetwork;
+		this.setVoltage(voltage);
+		this.setPhaseDegrees(phaseDegrees);
+		this.powerFactor = powerFactor;
+		this.isSlack = isSlack;
 	}
 
 	@Override
@@ -85,4 +92,11 @@ public class Consumer extends DecoratedHolonObject {
 		}
 	}
 
+	public boolean isSlack() {
+		return isSlack;
+	}
+
+	public double getPowerFactor() {
+		return powerFactor;
+	}
 }

+ 9 - 1
src/ui/model/DecoratedCable.java

@@ -9,10 +9,13 @@ public class DecoratedCable {
 	private Edge model;
 	private CableState state;
 	private float flowEnergy;
-	public DecoratedCable(Edge edge, CableState state, float flowEnergy){
+	private float powerLoss;
+
+	public DecoratedCable(Edge edge, CableState state, float flowEnergy, float powerLoss){
 		this.model = edge;
 		this.state = state;
 		this.flowEnergy = flowEnergy;
+		this.powerLoss = powerLoss;
 	}
 	public Edge getModel() {
 		return model;
@@ -23,6 +26,11 @@ public class DecoratedCable {
 	public float getFlowEnergy() {
 		return flowEnergy;
 	}
+
+	public float getPowerLoss() {
+		return powerLoss;
+	}
+
 	public void setFlowEnergy(float flowEnergy) {
 		this.flowEnergy = flowEnergy;
 	}

+ 19 - 0
src/ui/model/DecoratedHolonObject.java

@@ -8,6 +8,9 @@ public abstract class DecoratedHolonObject {
 	}
 	private HolonObject model;
 	private HolonObjectState state;
+	private double voltage;
+	private double phaseDegrees;
+
 	public DecoratedHolonObject(HolonObject objectToLookAt){
 		model = objectToLookAt;
 	}
@@ -21,4 +24,20 @@ public abstract class DecoratedHolonObject {
 	public void setState(HolonObjectState state) {
 		this.state = state;
 	}
+
+	public double getVoltage() {
+		return voltage;
+	}
+
+	public void setVoltage(double voltage) {
+		this.voltage = voltage;
+	}
+
+	public double getPhaseDegrees() {
+		return phaseDegrees;
+	}
+
+	public void setPhaseDegrees(double phaseDegrees) {
+		this.phaseDegrees = phaseDegrees;
+	}
 }

+ 4 - 5
src/ui/model/DecoratedNetwork.java

@@ -183,7 +183,7 @@ public class DecoratedNetwork {
 		//DecoratedCables
 		//Minimum demand first:
 		for(IntermediateCableWithState edge: minimumNetwork.getEdgeList()) {		
-			decoratedCableList.add(new DecoratedCable(edge.getModel(), edge.getState(), (edge.getState() == CableState.Working) ? energyToSupplyInTheNetwork : 0.0f));
+			decoratedCableList.add(new DecoratedCable(edge.getModel(), edge.getState(), (edge.getState() == CableState.Working) ? energyToSupplyInTheNetwork : 0.0f, 0));
 		}
 	}
 
@@ -237,11 +237,10 @@ public class DecoratedNetwork {
 			float energyNeeded = hObject.getEnergyNeededFromConsumingElementsWithFlex(Iteration, flexManager);
 			float energySelfProducing = hObject.getEnergySelfProducingFromProducingElementsWithFlex(Iteration, flexManager);
 			if(energyNeeded < energySelfProducing) {
-				Supplier sup = new Supplier(hObject, energySelfProducing - energyNeeded, energyNeeded);
+				Supplier sup = new Supplier(hObject, energySelfProducing - energyNeeded, energyNeeded, 0, 0, false);
 				supplierList.add(sup);
 			} else if (energyNeeded > energySelfProducing) {
-				Consumer con = new Consumer(hObject);
-				con.setEnergyNeededFromNetwork(energyNeeded - energySelfProducing);
+				Consumer con = new Consumer(hObject, energyNeeded - energySelfProducing, 0, 0, 0,false);
 				con.setMinimumConsumingElementEnergy(hObject.getMinimumConsumingElementEnergyWithFlex(Iteration, flexManager));
 				con.setEnergyFromConsumingElemnets(hObject.getEnergyNeededFromConsumingElementsWithFlex(Iteration, flexManager));
 				con.setEnergySelfSupplied(hObject.getEnergySelfProducingFromProducingElementsWithFlex(Iteration, flexManager));
@@ -252,7 +251,7 @@ public class DecoratedNetwork {
 					Passiv pas = new Passiv(hObject);
 					passivNoEnergyList.add(pas);
 				} else {
-					Consumer con = new Consumer(hObject);
+					Consumer con = new Consumer(hObject, 0.0f, 0, 0,0,false);
 					con.setEnergyNeededFromNetwork(0.0f);
 					con.setMinimumConsumingElementEnergy(hObject.getMinimumConsumingElementEnergyWithFlex(Iteration, flexManager));
 					con.setEnergyFromConsumingElemnets(hObject.getEnergyNeededFromConsumingElementsWithFlex(Iteration, flexManager));

+ 11 - 2
src/ui/model/Supplier.java

@@ -10,11 +10,16 @@ public class Supplier extends DecoratedHolonObject {
 	private float energyToSupplyNetwork;
 	private float energySupplied;
 	private float energySelfConsuming;
-	public Supplier(HolonObject objectToLookAt, float energyToSupplyNetwork, float energySelfConsuming) {
+	private boolean isSlack;
+
+	public Supplier(HolonObject objectToLookAt, float energyToSupplyNetwork, float energySelfConsuming, double voltage, double phaseDegrees, boolean isSlack) {
 		super(objectToLookAt);
 		this.energyToSupplyNetwork = energyToSupplyNetwork;
-		energySupplied = 0.0f;
+		this.energySupplied = 0.0f;
 		this.energySelfConsuming = energySelfConsuming;
+		this.setVoltage(voltage);
+		this.setPhaseDegrees(phaseDegrees);
+		this.isSlack = isSlack;
 	}
 
 	@Override
@@ -62,4 +67,8 @@ public class Supplier extends DecoratedHolonObject {
 	public String toString() {
 		return getModel().getName();
 	}
+
+	public boolean isSlack() {
+		return isSlack;
+	}
 }

+ 11 - 1
src/ui/view/GUI.java

@@ -397,7 +397,6 @@ public class GUI implements CategoryListener {
 					}
 					System.exit(1);
 				}
-				System.exit(0);
 			}
 		});
 
@@ -1661,6 +1660,8 @@ public class GUI implements CategoryListener {
 				btemp = model.getPropertyTable().getValueAt(selValueYBool,
 						selValueXBool);
 				// Edit modus for capacity by double clicking
+
+				// Capacity
 				if (selValueY == 2) {
 					Float ftemp;
 					if (Float.parseFloat(temp.toString()) >= 0.0) {
@@ -1670,6 +1671,15 @@ public class GUI implements CategoryListener {
 					}
 					model.getSelectedEdge().setCapacity(ftemp);
 				}
+
+				// Real length
+				else if (selValueY == 4) {
+					float realLength = Float.parseFloat(temp.toString());
+					if (realLength >= 0) {
+						model.getSelectedEdge().setRealLength(realLength);
+						controller.calculateStateAndVisualForCurrentTimeStep();
+					}
+				}
 				// Status edition through a check box
 				if (selValueYBool == 3) {
 					Boolean bbTemp = Boolean.parseBoolean(btemp.toString());

+ 5 - 28
src/ui/view/GroupNodeCanvas.java

@@ -355,19 +355,17 @@ public class GroupNodeCanvas extends AbstractCanvas implements MouseListener, Mo
 		g.fillRect(pos.x - controller.getScaleDiv2(), pos.y - controller.getScaleDiv2(), controller.getScale(), controller.getScale());
 		drawCanvasObject(g, decoratedHolonObject.getModel().getImage(), pos);
 	}
-	private void drawCanvasObjectString(Graphics2D g, Position posOfCanvasObject, float energy) {
-		g.setColor(Color.BLACK);
-		g.setFont(new Font("TimesNewRoman", Font.PLAIN, (int) (controller.getScale() / 4f) )); 
-		g.drawString((energy > 0)? "+" + Float.toString(energy): Float.toString(energy), posOfCanvasObject.x - controller.getScaleDiv2(), posOfCanvasObject.y - controller.getScaleDiv2() - 1);
+	private void drawCanvasObjectString(Graphics2D g, Position posOfCanvasObject, float energy, double voltage, double phase, double powerFactor, boolean slack) {
+	    MyCanvas.drawCanvasObjectString(controller, g, posOfCanvasObject, energy, voltage, phase, powerFactor, slack);
 	}
 	private void paintConsumer(Graphics2D g, Consumer con){
 		paintCanvasObject(g, con);
 		paintSupplyBar(g,con.getSupplyBarPercentage(), getStateColor(con.getState()), con.getModel().getPosition());
-		drawCanvasObjectString(g, con.getModel().getPosition(), -con.getEnergyNeededFromNetwork());
+		drawCanvasObjectString(g, con.getModel().getPosition(), -con.getEnergyNeededFromNetwork(), con.getVoltage(), con.getPhaseDegrees(), con.getPowerFactor(), con.isSlack());
 	}
 	private void paintSupplier(Graphics2D g, Supplier sup){
 		paintCanvasObject(g, sup);
-		drawCanvasObjectString(g, sup.getModel().getPosition(), sup.getEnergyToSupplyNetwork());
+		drawCanvasObjectString(g, sup.getModel().getPosition(), sup.getEnergyToSupplyNetwork(), sup.getVoltage(), sup.getPhaseDegrees(), -1, sup.isSlack());
 	}
 	
 	private void drawCanvasObject(Graphics2D g, String Image, Position pos) {
@@ -379,28 +377,7 @@ public class GroupNodeCanvas extends AbstractCanvas implements MouseListener, Mo
 	
 	private void paintCable(Graphics2D g, DecoratedCable cable, boolean isSelected)
 	{
-		Position start = cable.getModel().getA().getPosition();
-		Position end =  cable.getModel().getB().getPosition();
-		float currentEnergy = cable.getFlowEnergy();
-		float capacity = cable.getModel().getCapacity();
-		boolean unlimited = cable.getModel().isUnlimitedCapacity();
-		switch(cable.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;
-		}
-		if(isSelected){
-			g.setColor(Color.lightGray);
-		}
-		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);
+		MyCanvas.paintCable(controller, g, cable, isSelected, showConnectionInformation);
 	}
 	private void paintSwitch(Graphics2D g, DecoratedSwitch dSwitch)
 	{

+ 46 - 33
src/ui/view/MyCanvas.java

@@ -365,19 +365,29 @@ public class MyCanvas extends AbstractCanvas implements MouseListener,
 		g.fillRect(pos.x - controller.getScaleDiv2(), pos.y - controller.getScaleDiv2(), controller.getScale(), controller.getScale());
 		drawCanvasObject(g, decoratedHolonObject.getModel().getImage(), pos);
 	}
-	private void drawCanvasObjectString(Graphics2D g, Position posOfCanvasObject, float energy) {
+	public static void drawCanvasObjectString(Control controller, Graphics2D g, Position posOfCanvasObject, float energy, double voltage, double phaseDegrees, double powerFactor, boolean isSlack) {
 		g.setColor(Color.BLACK);
 		g.setFont(new Font("TimesNewRoman", Font.PLAIN, (int) (controller.getScale() / 4f) )); 
-		g.drawString((energy > 0)? "+" + Float.toString(energy): Float.toString(energy), posOfCanvasObject.x - controller.getScaleDiv2(), posOfCanvasObject.y - controller.getScaleDiv2() - 1);
+		g.drawString((energy > 0)? "+" + energy : Float.toString(energy), posOfCanvasObject.x - controller.getScaleDiv2(), posOfCanvasObject.y - controller.getScaleDiv2() - 1);
+
+		// Only show voltage when given
+		if (voltage > 0) {
+			if (powerFactor >= 0)
+				g.drawString(String.format("%.0f V (%.1f °) PF: %d%%", voltage, phaseDegrees, Math.round(powerFactor * 100)), posOfCanvasObject.x - controller.getScaleDiv2(), posOfCanvasObject.y - controller.getScaleDiv2() - 14);
+			else
+				g.drawString(String.format("%.0f V (%.1f °)", voltage, phaseDegrees), posOfCanvasObject.x - controller.getScaleDiv2(), posOfCanvasObject.y - controller.getScaleDiv2() - 14);
+		}
+		if (isSlack)
+			g.drawString("SLACK", posOfCanvasObject.x - controller.getScaleDiv2(), posOfCanvasObject.y - controller.getScaleDiv2() - 28);
 	}
 	private void paintConsumer(Graphics2D g, Consumer con){
 		paintCanvasObject(g, con);
 		paintSupplyBar(g,con.getSupplyBarPercentage(), getStateColor(con.getState()), con.getModel().getPosition());
-		drawCanvasObjectString(g, con.getModel().getPosition(), -con.getEnergyNeededFromNetwork());
+		drawCanvasObjectString(controller, g, con.getModel().getPosition(), -con.getEnergyNeededFromNetwork(), con.getVoltage(), con.getPhaseDegrees(), con.getPowerFactor(), con.isSlack());
 	}
 	private void paintSupplier(Graphics2D g, Supplier sup){
 		paintCanvasObject(g, sup);
-		drawCanvasObjectString(g, sup.getModel().getPosition(), sup.getEnergyToSupplyNetwork());
+		drawCanvasObjectString(controller, g, sup.getModel().getPosition(), sup.getEnergyToSupplyNetwork(), sup.getVoltage(), sup.getPhaseDegrees(), -1, sup.isSlack());
 	}
 	
 	private void drawCanvasObject(Graphics2D g, String Image, Position pos) {
@@ -387,46 +397,52 @@ public class MyCanvas extends AbstractCanvas implements MouseListener,
 				controller.getScale(), controller.getScale() , null);
 	}
 	
-	private void paintCable(Graphics2D g, DecoratedCable cable, boolean isSelected)
-	{
-		Position start = cable.getModel().getA().getPosition();
-		Position end =  cable.getModel().getB().getPosition();
+	public static void paintCable(Control controller, Graphics2D g, DecoratedCable cable, boolean isSelected, boolean showConnectionInformation) {
+		// Find cable start/end
+		AbstractCanvasObject a = cable.getModel().getA();
+		AbstractCanvasObject b = cable.getModel().getB();
+		Position start = a.getPosition();
+		Position end = b.getPosition();
+
+		// Find cable state
 		float currentEnergy = Math.abs(cable.getFlowEnergy());
 		float capacity = cable.getModel().getCapacity();
 		boolean unlimited = cable.getModel().isUnlimitedCapacity();
-		switch(cable.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 (cable.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 : (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)
-			drawArrow(g, start.x, start.y, end.x, end.y);
+			drawArrow(g, start.x, start.y, end.x, end.y, (b instanceof Node) ? 5 : 30);
 		else if (cable.getFlowEnergy() < -2)
-			drawArrow(g, end.x, end.y, start.x, start.y);
+			drawArrow(g, end.x, end.y, start.x, start.y, (a instanceof Node) ? 5 : 30);
 		else
 			g.drawLine(start.x, start.y, end.x, end.y);
 
-		if(showConnectionInformation) {
+		if (showConnectionInformation) {
 			double dx = start.x - end.x, dy = start.y - end.y;
-			double angle = Math.atan2(dy, dx);
-			int offsetX = (int)(Math.sin(angle - Math.PI) * 15);
-			int offsetY = (int)(Math.cos(angle - Math.PI) * 15);
+			int offsetX = dx < 20 ? 15 : 0;
+			int offsetY = dy < 20 ? 15 : 0;
 
 			Position middle = new Position((start.x + end.x) / 2 + offsetX, (start.y + end.y) / 2 + offsetY);
-			g.setFont(new Font("TimesRoman", Font.PLAIN, Math.max((int) (controller.getScale() / 3.5f), 10) )); 
-			g.drawString(String.format("%.1f / %s", currentEnergy, (unlimited?"\u221E":capacity)) , middle.x, middle.y);
+			g.setFont(new Font("TimesRoman", Font.PLAIN, Math.max((int) (controller.getScale() / 3.5f), 10)));
+			g.drawString(String.format("%.1f W / %s W", currentEnergy, (unlimited ? "\u221E" : capacity)), middle.x, middle.y);
+			g.drawString(String.format("%.1f km loss: %.1f W", cable.getModel().getRealLength(), Math.abs(cable.getPowerLoss())), middle.x, middle.y + 14);
 		}
 	}
 
-	private void drawArrow(Graphics g1, int x1, int y1, int x2, int y2) {
+	private static void drawArrow(Graphics g1, int x1, int y1, int x2, int y2, int offset) {
 		Graphics2D g = (Graphics2D) g1.create();
 
 		double dx = x2 - x1, dy = y2 - y1;
@@ -437,11 +453,9 @@ public class MyCanvas extends AbstractCanvas implements MouseListener,
 		g.transform(at);
 
 		final int ARR_SIZE = 15;
-		final int offset = 30;
-
 		len -= offset;
 
-		g.drawLine(0, 0, len, 0);
+		g.drawLine(0, 0, len - ARR_SIZE, 0);
 		g.fillPolygon(new int[]{len, len - ARR_SIZE, len - ARR_SIZE, len}, new int[]{0, -ARR_SIZE / 2, ARR_SIZE / 2, 0}, 4);
 	}
 
@@ -591,12 +605,11 @@ public class MyCanvas extends AbstractCanvas implements MouseListener,
 		
 		VisualRepresentationalState  visualState = controller.getSimManager().getActualVisualRepresentationalState();
 		//VisualState Representation:
-		if(visualState == null) System.out.println("AHHH"); 
 		for(ExitCable cable : visualState.getExitCableList()) {
 			paintExitCable(g2d, cable);
 		}
 		for(DecoratedCable cable : visualState.getCableList()) {
-			paintCable(g2d, cable, selectedEdges.contains(cable.getModel()));
+			paintCable(controller, g2d, cable, selectedEdges.contains(cable.getModel()), showConnectionInformation);
 		}
 		for(DecoratedGroupNode dGroupNode : visualState.getGroupNodeList()) {
 			paintGroupNode(g2d, dGroupNode);
@@ -611,7 +624,7 @@ public class MyCanvas extends AbstractCanvas implements MouseListener,
 			paintCanvasObject(g2d, pas);
 		}
 		for(DecoratedSwitch dSwitch : visualState.getSwitchList()) {
-				paintSwitch(g2d, dSwitch);
+			paintSwitch(g2d, dSwitch);
 		}
 		for(Node node : visualState.getNodeList()) {
 			drawCanvasObject(g2d, "/Images/node.png" , node.getPosition());