diff --git a/SchematicDrawing/Enums.cs b/SchematicDrawing/Enums.cs index b5892c6b0..d896297bb 100644 --- a/SchematicDrawing/Enums.cs +++ b/SchematicDrawing/Enums.cs @@ -11,6 +11,7 @@ namespace SchematicDrawing public static class Const { public const int GridSize = 10; + public const int SecondaryStart = 10000; public const int Vacuum = 1000000; } diff --git a/SchematicDrawing/GNode.cs b/SchematicDrawing/GNode.cs index eeb4a916e..d9e585e39 100644 --- a/SchematicDrawing/GNode.cs +++ b/SchematicDrawing/GNode.cs @@ -1,23 +1,35 @@ -using System; +/// +/// Copyright (c) 2020 Sensus Slovensko a.s. +/// +using System; using System.Collections.Generic; namespace SchematicDrawing { public class GNode { + /// Graph node properties public IDrawingItem Item; public int NodeId; public readonly IList Edges; + /// Distance from a start node public int Dist; - public bool Visited; + /// Minimal heap index + public int HeapIx; + + /// + /// Constructor + /// + /// Item + /// public GNode(IDrawingItem item, int nodeId) { - this.Item = item; - this.NodeId = nodeId; - this.Edges = new List(); - Reset(); + Item = item; + NodeId = nodeId; + Edges = new List(); + Dist = int.MaxValue; } public void AddEdge(GEdge edge) @@ -25,15 +37,16 @@ namespace SchematicDrawing Edges.Add(edge); } - public void Reset() + public GNode Reset(int heapIx, int dist = int.MaxValue) { - Visited = false; - Dist = int.MaxValue; + HeapIx = heapIx; + Dist = dist; + return this; } public override string ToString() { - return string.Format("{0}/{1} {2} dist={3}", Item.Name, NodeId, Visited ? "V" : "", Dist); + return string.Format("{0}/{1} dist={2}", Item.Name, NodeId, Dist); } } } diff --git a/SchematicDrawing/Graph.cs b/SchematicDrawing/Graph.cs index 061b35725..05cd6421b 100644 --- a/SchematicDrawing/Graph.cs +++ b/SchematicDrawing/Graph.cs @@ -1,4 +1,8 @@ -using System; +/// +/// Copyright (c) 2020 Sensus Slovensko a.s. +/// +using System; +using System.Diagnostics; using System.Collections.Generic; namespace SchematicDrawing @@ -6,25 +10,47 @@ namespace SchematicDrawing public class Graph { IList nodes; + GNode startNode; + int secondaryStartNodesCount; + + MinHeap minHeap; GNode currentNode; public Graph() { nodes = new List(); + startNode = null; + secondaryStartNodesCount = 0; + minHeap = new MinHeap(); } public void Clear() { nodes.Clear(); + startNode = null; + secondaryStartNodesCount = 0; } /// /// Add a node to the list of graph nodes /// /// Graph node - public void AddNode(GNode node) + public void AddNode(GNode node, bool isStartNode = false, bool isSecondaryStartNode = false) { - nodes.Add(node); + if (isStartNode && startNode == null) + { + startNode = node; + nodes.Insert(0, node); + } + else if (isStartNode || isSecondaryStartNode) + { + secondaryStartNodesCount++; + nodes.Insert(startNode != null ? 1 : 0, node); + } + else + { + nodes.Add(node); + } } /// @@ -47,44 +73,30 @@ namespace SchematicDrawing /// /// Start node /// List of secondary start nodes - public void RunDijkstraAlgo(GNode startNode, IList secondaryStartNodes = null) + public void RunDijkstraAlgo() { - if (nodes == null || startNode == null || !nodes.Contains(startNode)) return; + if (nodes == null || startNode == null) return; /// Reset all nodes - foreach (var node in nodes) node.Reset(); + minHeap.ResetAndBuild(nodes, secondaryStartNodesCount); - startNode.Dist = 0; - if (secondaryStartNodes != null) foreach (var ssn in secondaryStartNodes) ssn.Dist = 10000; - currentNode = startNode; - - int minDistance = int.MaxValue; + currentNode = minHeap.ExtractMinNode(); do { /// Consider all unvisited neighbors of the current node foreach (var e in currentNode.Edges) { int distViaCurrNode = (e.Dist == int.MaxValue) ? int.MaxValue : currentNode.Dist + e.Dist; - if (!e.EndNode.Visited && distViaCurrNode < e.EndNode.Dist) + if (distViaCurrNode < e.EndNode.Dist) { e.EndNode.Dist = distViaCurrNode; + minHeap.SiftUp(e.EndNode.HeapIx); } } - currentNode.Visited = true; - /// Find new current node from all unvisited nodes - currentNode = null; - minDistance = int.MaxValue; - foreach (var n in nodes) - { - if (!n.Visited && n.Dist < minDistance) - { - minDistance = n.Dist; - currentNode = n; - } - } + currentNode = minHeap.ExtractMinNode(); } - while (minDistance < int.MaxValue && currentNode != null); + while (currentNode.Dist < int.MaxValue && currentNode != null); } } } diff --git a/SchematicDrawing/MinHeap.cs b/SchematicDrawing/MinHeap.cs new file mode 100644 index 000000000..afd1f22a4 --- /dev/null +++ b/SchematicDrawing/MinHeap.cs @@ -0,0 +1,134 @@ +/// +/// Copyright (c) 2020 Sensus Slovensko a.s. +/// +using System; +using System.Collections.Generic; + +namespace SchematicDrawing +{ + public class MinHeap + { + GNode[] heap; /// Minimum heap, a binary tree in form of an array + int heapSize; /// Heap size (not necessarily a size of the array heap[]) + + /// + /// Constructor + /// + public MinHeap() + { + heap = null; + heapSize = 0; + } + + /// + /// Reset nodes and build the initial minimum heap tree. + /// + /// First node on the list is the start node (mostly the main water tank), + /// followed by 'secondaryStartNodesCount' secondary start nodes (typically + /// tanks above scales), followed by all remaining nodes of the drawing. + /// In this way the heap is heapified from the beginning. + /// + /// Graph nodes + /// Number of secondary start nodes + public void ResetAndBuild(IList nodes, int secondaryStartNodesCount) + { + if (nodes == null || nodes.Count == 0) return; + + if (heapSize != nodes.Count) + { + heapSize = nodes.Count; + heap = new GNode[nodes.Count]; + } + + heap[0] = nodes[0].Reset(0, 0); + for (int i = 1; i <= secondaryStartNodesCount; i++) + { + heap[i] = nodes[i].Reset(i, Const.SecondaryStart); + } + for (int i = secondaryStartNodesCount + 1; i < nodes.Count; i++) + { + heap[i] = nodes[i].Reset(i, int.MaxValue); + } + } + + /// + /// Returns the node with minimal distance at the top of the heap binary tree. + /// This node is removed from the heap, replaced by the last node and the heap is 'Heapified'. + /// + /// Node with minimal distance + public GNode ExtractMinNode() + { + GNode minNode = heap[0]; + if (--heapSize > 0) + { + heap[0] = heap[heapSize]; /// Swap with the last one + heap[0].HeapIx = 0; + heap[heapSize] = minNode; + SiftDown(0); /// Heapify the heap, sift down the swapped (formerly the last) item + } + return minNode; + } + + /// + /// Sift up the node in the minimum heap binary tree. + /// Function is called when distance of a node in the graph is updated. + /// As the distance is always decreased, the node sifts up in the tree. + /// + /// Index of the node to sift up + public void SiftUp(int heapIx) + { + if (heapIx == 0) return; + int parentIx = (heapIx - 1) / 2; + if (heap[heapIx].Dist < heap[parentIx].Dist) + { + SwapNodes(heapIx, parentIx); + SiftUp(parentIx); + } + } + + /// + /// Sift down the node in the minimum heap binary tree. + /// Function is called when node at the top of the tree is extracted and replaced + /// by the last node. This last node is then sift down according to its distance. + /// + /// Index of the node to sift down + void SiftDown(int heapIx) + { + int leftIx = 2 * heapIx + 1; + if (leftIx < heapSize) + { + int minIx = heapIx; + if (heap[leftIx].Dist < heap[minIx].Dist) + { + minIx = leftIx; + } + + int rightIx = 2 * heapIx + 2; + if (rightIx < heapSize && heap[rightIx].Dist < heap[minIx].Dist) + { + minIx = rightIx; + } + + if (minIx != heapIx) + { + SwapNodes(heapIx, minIx); + SiftDown(minIx); + } + } + } + + /// + /// Swap two nodes in the minimum heap binary tree. + /// + /// Index of the 1st node + /// Index of the 2nd node + void SwapNodes(int ix1, int ix2) + { + GNode swap = heap[ix1]; + heap[ix1] = heap[ix2]; + heap[ix1].HeapIx = ix1; + heap[ix2] = swap; + heap[ix2].HeapIx = ix2; + } + } +} diff --git a/SchematicDrawing/SchematicDrawing.csproj b/SchematicDrawing/SchematicDrawing.csproj index 3a65d2203..6998cc7d8 100644 --- a/SchematicDrawing/SchematicDrawing.csproj +++ b/SchematicDrawing/SchematicDrawing.csproj @@ -56,6 +56,7 @@ + diff --git a/SchematicDrawing/SchematicDrawingCtrl.cs b/SchematicDrawing/SchematicDrawingCtrl.cs index 991250212..51d3755f1 100644 --- a/SchematicDrawing/SchematicDrawingCtrl.cs +++ b/SchematicDrawing/SchematicDrawingCtrl.cs @@ -7,6 +7,7 @@ using System.Drawing; using System.Windows.Forms; using Dirichlet.Numerics; using System.Numerics; +using System.Diagnostics; namespace SchematicDrawing { @@ -54,7 +55,8 @@ namespace SchematicDrawing Utils.UpdateGraphEdges(route, it as IRouteBasedDrawingItem); } } - graph.RunDijkstraAlgo(startNode, secondaryStartNodes); + + graph.RunDijkstraAlgo(); Redraw(); } } @@ -231,8 +233,6 @@ namespace SchematicDrawing IList items; Graph graph; - GNode startNode; - IList secondaryStartNodes; /// /// Rectangles of elements of drawn items @@ -287,8 +287,6 @@ namespace SchematicDrawing pipesXL = new List(); graph = new Graph(); - startNode = null; - secondaryStartNodes = new List(); ItemElementRectangles = new List(); @@ -313,7 +311,7 @@ namespace SchematicDrawing { GNode gnode = new GNode(item, nid); item.GNodes.Add(gnode); - graph.AddNode(gnode); + graph.AddNode(gnode, dshape.Shape == Shape.Tank, dshape.Shape == Shape.Scale); } foreach (var edge in dshape.Edges) @@ -324,16 +322,6 @@ namespace SchematicDrawing (item as IRouteBasedDrawingItem).EdgesToSet.Add(gedge); } } - - if (dshape.Shape == Shape.Tank && item.GNodes.Count > 0) - { - startNode = item.GNodes[0]; - } - - if (dshape.Shape == Shape.Scale && item.GNodes.Count > 0) - { - secondaryStartNodes.Add(item.GNodes[0]); - } } } @@ -344,8 +332,6 @@ namespace SchematicDrawing items.Clear(); Name2Item.Clear(); graph.Clear(); - startNode = null; - secondaryStartNodes.Clear(); Redraw(); } public IList GetItems() @@ -388,11 +374,6 @@ namespace SchematicDrawing return tempPipe; } - public void RunDijkstraAlgo() - { - graph.RunDijkstraAlgo(startNode, secondaryStartNodes); - } - void PrepareFontsPensBrushesEtc() { /// Font for drawing labels @@ -474,6 +455,10 @@ namespace SchematicDrawing /// PaintEventArgs void SchematicDrawingCtrl_Paint(object sender, PaintEventArgs e) { + /// Profiling + //stopwatch.Reset(); + //stopwatch.Start(); + e.Graphics.Clear(Color.White); ItemElementRectangles.Clear(); @@ -669,15 +654,37 @@ namespace SchematicDrawing } } } + + /// Profiling + //stopwatch.Stop(); + //if (firstTime) + //{ + // firstTime = false; + //} + //else if (totalTimes < long.MaxValue) + //{ + // totalMs += stopwatch.ElapsedMilliseconds; + // totalTicks += stopwatch.ElapsedTicks; + // totalTimes++; + // Console.WriteLine("Average Paint duration is {0} ms = {1} ticks", totalMs / totalTimes, totalTicks / totalTimes); + //} } + /// Profiling + //Stopwatch stopwatch = new Stopwatch(); + //bool firstTime = true; + //long totalMs = 0; + //long totalTicks = 0; + //long totalTimes = 0; + + /// - /// Draw a pipe + /// Draw a pipe. /// /// PaintEventArgs - /// Edge string - /// Edge size - /// true = filled, false = empty + /// Pipe object + /// Distance from source of water for coloring + /// true = highlighted (edit mode) void DrawPipe(PaintEventArgs e, Pipe pipe, int distance, bool highlighted = false) { int x1, y1, x2, y2; @@ -689,6 +696,13 @@ namespace SchematicDrawing } } + /// + /// Draw an item (body). + /// + /// PaintEventArgs + /// IDrawingItem object + /// DrawingShape + /// Rectangle where item body is drawn Rectangle DrawItem(PaintEventArgs e, IDrawingItem item, DrawingShape dsh) { bool routeBit = false; @@ -810,9 +824,10 @@ namespace SchematicDrawing /// X-coordinate /// Y-coordinate /// Bitfield of elements to be searched for - /// Found component or edge or null - /// Fount node id or 0 - /// Element enum value (Element.None if nothing found) + /// Found item or null (in case of body, node, label, measured value or setpoint) + /// Found pipe or null (in case of a pipe) + /// Found node id or 0 (in case of an item node) + /// Element enum value (Element.None when nothing was found) public Element FindElement(int x, int y, Element elements, out IDrawingItem item, out Pipe pipe, out int nodeId) { const int NodeTolerance = 5; @@ -848,7 +863,7 @@ namespace SchematicDrawing } /// - /// + /// Searches for an item body, label, measured value or setpoint. /// /// X coordinate /// Y coordinate