public bool Add(Vertex[] Edge1, Vertex[] Edge2) { PointF[] edge1 = new PointF[2]; PointF[] edge2 = new PointF[2]; edge1 = VertToPoint(Edge1); edge2 = VertToPoint(Edge2); HashSet<PointF> crossing = new HashSet<PointF>(); crossing.Add(edge1[0]); crossing.Add(edge1[1]); crossing.Add(edge2[0]); crossing.Add(edge2[1]); foreach (HashSet<PointF> storedCrossing in set) { if (crossing.SetEquals(storedCrossing)) // Crossing already exists in set, so return false and don't add { return false; } } set.Add(crossing); return true; }
internal Vertex[] GenerateVertices(int verticesAmt) { Vertex[] vertices = new Vertex[verticesAmt]; for (int i = 0; i < verticesAmt; i++) { // Random Position int x = StaticRandom.Rand(100, 400); int y = StaticRandom.Rand(100, 400); // Random Connections int connections = StaticRandom.Rand(0, 2); HashSet<int> connectionSet = new HashSet<int>(); if (i > 0) connectionSet.Add(StaticRandom.Rand(0, i - 1)); for (int c = 0; c < connections; c++) { int conn = StaticRandom.Rand(0, verticesAmt); if (conn != i) connectionSet.Add(conn); } // The ID is its position in the array vertices[i] = new Vertex(i, new Vector(x, y), connectionSet); } // Each of the connections should go both ways. for (int i = 0; i < verticesAmt; i++) foreach (int connected in vertices[i].connectedVertexIDs) vertices[connected].AddConnection(i); return vertices; }
internal override Vertex[] UpdateForces(int VerticesAmt, Vertex[] Vertices, double aWeight, double rWeight, double k = 0) { // Works the same way as array, but can be used concurrently (as we're both reading and writing) var forcesDict = new Dictionary<int, Vector>(); var closed = new Dictionary<int, bool>(); // Needs to be instantiated to something for (int i = 0; i < VerticesAmt; i++) forcesDict[i] = new Vector(0, 0); for (int i = 0; i < VerticesAmt; i++) closed[i] = false; for (int i = 0; i < VerticesAmt; i++) { foreach (int connection in Vertices[i].connectedVertexIDs) { if (closed[connection]) continue; // Omdat FurchtRein raar is: // radius <- rWeight, // rWeight == aWeight <- aWeight // c <- k Double FRConstant = Algorithms.FruchtReinConstant(Vertices[i], Vertices, k, rWeight); Vector aForce = Algorithms.FruchtReinAttractive(Vertices[i], Vertices[connection], FRConstant, aWeight); forcesDict[i] += aForce; } closed[i] = true; } // Add Repulsive Forces Into the mix: for (int i = 0; i < VerticesAmt; i++) closed[i] = false; for (int i = 0; i < VerticesAmt; i++) { for (int j = 0; j < VerticesAmt; j++) { if (closed[j] || i == j) continue; Double FRConstant = Algorithms.FruchtReinConstant(Vertices[i], Vertices, k, rWeight); Vector rForce = Algorithms.FruchtReinRepulsive(Vertices[i], Vertices[j], FRConstant, aWeight); forcesDict[i] += rForce; } closed[i] = true; } // Apply the forces for (int i = 1; i < VerticesAmt; i++) Vertices[i].ApplyForce(forcesDict[i]); return Vertices; }
public static Vector EadesAttractive(Vertex node1, Vertex node2, double aWeight, double aWeight2) { Vector r = Vertex.VectorBetween(node2, node1); double distance = Math.Abs(r.Length); r.Normalize(); Vector forceVector = r * Math.Log(distance / aWeight2, 2); return aWeight * forceVector; }
public static Vector EadesRepulsive(Vertex node1, Vertex node2, double rWeight) { Vector r = Vertex.VectorBetween(node2, node1); double distance = Math.Abs(r.Length); r.Normalize(); Vector forceVector = r / (distance * distance); return rWeight * forceVector; }
public static Vector FruchtReinAttractive(Vertex node1, Vertex node2, double k, double weight) { Vector r = Vertex.VectorBetween(node1, node2); double distance = Math.Abs(r.Length); r.Normalize(); Vector forceVector = r * (distance * distance) / k; return weight * forceVector; }
public static Vector FruchtReinRepulsive(Vertex node1, Vertex node2, double k, double weight) { Vector r = Vertex.VectorBetween(node1, node2); double distance = Math.Abs(r.Length); r.Normalize(); Vector forceVector = r * -(k * k) / distance; return weight * forceVector; }
/// <summary> /// Calculate the attractive force between two vertices. /// This is done using Hooke's Algorithm /// </summary> /// <param name="node1"></param> /// <param name="node2"></param> /// <param name="aWeight"></param> /// <returns></returns> public static Vector HCAttractive(Vertex node1, Vertex node2, double aWeight) { Vector r = Vertex.VectorBetween(node1, node2); double distance = Math.Abs(r.Length); r.Normalize(); Vector forceVector = r * (distance - 1); return aWeight * forceVector; }
private PointF[] VertToPoint(Vertex[] input) { PointF[] edge = new PointF[2]; edge[0].X = (float)input[0].PositionVector.X; edge[0].Y = (float)input[0].PositionVector.Y; edge[1].X = (float)input[1].PositionVector.X; edge[1].Y = (float)input[1].PositionVector.Y; return edge; }
/// <summary> /// Calculates the translation for Vertex node1 based on the repulsive and attractive forces between it and node2. /// The attractive force is calculated according to a logarithmic variation on Hooke's law while the repulsive force is calculated according to Coulomb's law. /// The total translation for node1 is then the sum of the translations on node1 based on every other node. /// </summary> /// <param name="node1">The vertex to be translated</param> /// <param name="node2">The vertex that's interacting with node1 via repulsive and attractive forces</param> /// <param name="c1">A constant that determines the strength of the attractive force</param> /// <param name="c2">A constant that determines the logarithmic scaling factor of the attractive force</param> /// <param name="c3">A constant that determines the strength of the repulsive force</param> /// <param name="s">The ratio between the size of the translation and the size of the combined attractive and repulsive force</param> /// <returns>The translation vector to be applied to node1</returns> public static Vector EadesForce(Vertex node1, Vertex node2, double c1, double c2, double c3, double s) { Vector r = Vertex.VectorBetween(node2, node1); Vector rn = r; rn.Normalize(); double d = r.Length; Vector fAtt = node1.ConnectedWith(node2) ? c1 * Math.Log(d / c2, 2) * rn : new Vector(0, 0); Vector fRep = (c3 / (d * d)) * rn; return s * (fAtt + fRep); }
/// <summary> /// Calculates the translation for Vertex node1 based on the repulsive and attractive forces between it and node2. /// The attractive and repulsive forces are calculated according to the optimal vertex distribution based on the algorithm of Fruchterman and Reingold /// The total translation for node1 is then the sum of the translations on node1 based on every other node. /// </summary> /// <param name="node1">The vertex to be translated</param> /// <param name="node2">The vertex that's interacting with node1 via repulsive and attractive forces</param> /// <param name="c">A constant that determines the weight of the optimal vertex distribution parameter</param> /// <param name="radius">A constant that determines the radius around the vertex to count the vertices in</param> /// <param name="s">The ratio between the size of the translation and the size of the combined attractive and repulsive force</param> /// <returns>The translation vector to be applied to node1</returns> public static Vector FruchtRein(Vertex node1, Vertex node2, double c, double radius, double s) { Vector r = Vertex.VectorBetween(node1, node2); Vector rn = r; rn.Normalize(); double d = r.Length; double k = c * Math.Sqrt((Math.PI * radius * radius) / (1)); // Function to count number of objects in radius around Vertex v here Vector fAtt = node1.ConnectedWith(node2) ? ((d * d) / k) * rn : new Vector(0, 0); Vector fRep = (-(k * k) / d) * rn; return s * (fAtt + fRep); }
// Converts a list of vertices to a list of strings // Helper function for CreateGraphs private static List<string> GraphToStrings(Vertex[] vertices) { List<string> lines = new List<string>(); string id, x, y, connections; for (int i = 0; i < vertices.Length; i++) { id = vertices[i].ID.ToString(); x = vertices[i].PositionVector.X.ToString(); y = vertices[i].PositionVector.Y.ToString(); connections = string.Join(",", vertices[i].connectedVertexIDs.ToArray()); lines.Add(id + " " + x + " " + y + " " + connections); } return lines; }
public void DrawGraph(Graphics g, Vertex[] vertices) { RectangleF node; foreach (var vertex in vertices) { // Draw the vertex PointF vertexPos = new PointF((float)vertex.PositionVector.X, (float)vertex.PositionVector.Y); node = new RectangleF(vertexPos.X - (nodeSize / 2f), vertexPos.Y - (nodeSize / 2f), nodeSize, nodeSize); g.FillEllipse(brush, node); g.DrawEllipse(pen1, node); // Draw the connections PointF connectedVertPos; foreach (var id in vertex.connectedVertexIDs) { connectedVertPos = new PointF((float)vertices[id].PositionVector.X, (float)vertices[id].PositionVector.Y); g.DrawLine(pen2, vertexPos, connectedVertPos); } } }
//, int[] qualityValues) // Stores a graph in .txt format public static bool SaveGraph(double[] paramStrings, Vertex[] vertices) { double[] nameDoubles = new double[paramStrings.Length]; for (int i = 0; i < paramStrings.Length; i++) nameDoubles[i] = paramStrings[i] * 10; String fileName = "/output/graph" + String.Join("", nameDoubles) + ".txt"; // Check whether the file already exists, we don't want to overwrite it if (File.Exists(Directory.GetCurrentDirectory() + fileName)) return false; String[] vertexStrings = new string[vertices.Length]; for (int i = 0; i < vertices.Length; i++) vertexStrings[i] = vertices[i].ToString(); File.WriteAllLines(Directory.GetCurrentDirectory() + fileName, vertexStrings); return true; }
public static Vector VectorBetween(Vertex node1, Vertex node2) { return node2.PositionVector - node1.PositionVector; }
// Generates a specified number of vertices and their connections // Guaranteed is that the graph that these vertices form will be completely connected, // ie every vertex can be directly or indirectly reached by every other vertex public static Vertex[] GenerateVertices(int amount) { for (int i = 0; i < amount; i++) { // Random Position //int x = rndGen.Next(100, 400); //int y = rndGen.Next(100, 400); double x = rndGen.NextDouble(); double y = rndGen.NextDouble(); // Random Connections int connections = rndGen.Next(2); HashSet<int> connectionSet = new HashSet<int>(); HashSet<int> connectedVertices = new HashSet<int>(); int connection; do { connection = connectedVertices.Count > 0 ? connectedVertices.ToList()[rndGen.Next(connectedVertices.Count)] : rndGen.Next(amount); } while (connection == i); connectionSet.Add(connection); connectedVertices.Add(i); for (int c = 0; c < connections; c++) { int conn = rndGen.Next(amount); if (conn != i) { connectionSet.Add(conn); connectedVertices.Add(i); connectedVertices.Add(conn); } } // The ID is its position in the array Vertices[i] = new Vertex(i, new Vector(x, y), connectionSet); } // Each of the connections should go both ways. for (int i = 0; i < amount; i++) { foreach (int connected in Vertices[i].connectedVertexIDs) { Vertices[connected].AddConnection(i); } } return Vertices; }
// General function to calculate the repulsive force for each algorithm private static Vector RepulsiveForce(AlgorithmType type, Vertex node1, Vertex node2, double k) { switch (type) { case AlgorithmType.HookeCoulomb: return Algorithms.HCRepulsive(node1, node2, rWeight); case AlgorithmType.Eades: return Algorithms.EadesRepulsive(node1, node2, rWeight); case AlgorithmType.FruchtRein: return Algorithms.FruchtReinRepulsive(node1, node2, k, FRWeight); default: return new Vector(0, 0); } }
public static double FruchtReinConstant(Vertex node, Vertex[] graph, double c, double radius) { return c * Math.Sqrt((Math.PI * radius * radius) / CountVertices(node, graph, radius)); }
public static int GetEdgeCrossings(Vertex[] vertices) { int edgeCrossings = 0; //Console.WriteLine("Getting edge crossings..."); EdgeCrossingSet done = new EdgeCrossingSet(); foreach (var vertex in vertices) { foreach (var connectedVert in vertex.connectedVertexIDs) { Vertex[] edge = { vertex, vertices[connectedVert] }; foreach (var otherEdgeVert1 in vertices) { foreach (var otherEdgeVert2 in otherEdgeVert1.connectedVertexIDs) { Vertex[] otherEdge = { otherEdgeVert1, vertices[otherEdgeVert2] }; if (CheckCross(edge, otherEdge)) { if (done.Add(edge, otherEdge)) edgeCrossings++; } } } } } return edgeCrossings; }
private static double CountVertices(Vertex node, Vertex[] graph, double radius) { int count = 0; foreach (Vertex v in graph) if (Math.Abs(Vertex.VectorBetween(node, v).Length) <= radius) count++; return count; }
// Calculates the total amount of unique edges public static double getTotalEdges(Vertex[] vertices) { List<Tuple<int, int>> edgeList = new List<Tuple<int, int>>(); Tuple<int, int> index, inverseIndex; for (int i = 0; i < vertices.Length; i++) { foreach(int id in vertices[i].connectedVertexIDs) { index = Tuple.Create<int, int>(i, id); inverseIndex = Tuple.Create<int, int>(id, i); if (!edgeList.Contains(index) && !edgeList.Contains(inverseIndex)) edgeList.Add(index); } } return edgeList.Count; }
// Calculates the dispersion of the edge lengths for each vertex using the coefficient of variation (standard deviation / median) // This is usually a value between 0 and 1, though it can be up to sqrt(n - 1) with n the size fo the data set public static double edgeLengthDispersion(Vertex[] vertices) { Dictionary<Tuple<int, int>, double> edgeDict = new Dictionary<Tuple<int, int>, double>(); Tuple<int, int> index, inverseIndex; for (int i = 0; i < vertices.Length; i++) { foreach (int id in vertices[i].connectedVertexIDs) { index = Tuple.Create<int, int>(i, id); inverseIndex = Tuple.Create<int, int>(id, i); if (!edgeDict.ContainsKey(index) && !edgeDict.ContainsKey(inverseIndex)) edgeDict.Add(index, Math.Abs(Vertex.VectorBetween(vertices[i], vertices[id]).Length)); } } return standardDeviation(edgeDict.Values.ToArray()) / edgeDict.Values.Average(); }
/// <summary> /// Calculate the repulsive force between two vertices. /// This is done using Coulomb's Algorithm /// </summary> /// <param name="node1"></param> /// <param name="node2"></param> /// <param name="rWeight"></param> /// <returns></returns> public static Vector HCRepulsive(Vertex node1, Vertex node2, double rWeight) { // The vector between the two vertices (basically the line connecting them) Vector r = Vertex.VectorBetween(node1, node2); double distance = Math.Abs(r.Length); r.Normalize(); Vector forceVector = -r / (distance * distance); return rWeight * forceVector; }
public static double qualityTest(Vertex[] vertices) { return GetEdgeCrossings(vertices) / getTotalEdges(vertices) + edgeLengthDispersion(vertices) + vertexDensityDispersion(vertices); }
// Add a new connection // Should not be used when the nodes are properly generated public void AddConnection(Vertex otherVertex) { this.connectedVertexIDs.Add(otherVertex.ID); }
// Check whether two line segments cross each other // Source: http://csharphelper.com/blog/2014/08/determine-where-two-lines-intersect-in-c/ private static bool CheckCross(Vertex[] line1, Vertex[] line2) { PointF p1 = new PointF((float)line1[0].PositionVector.X, (float)line1[0].PositionVector.Y); PointF p2 = new PointF((float)line1[1].PositionVector.X, (float)line1[1].PositionVector.Y); PointF p3 = new PointF((float)line2[0].PositionVector.X, (float)line2[0].PositionVector.Y); PointF p4 = new PointF((float)line2[1].PositionVector.X, (float)line2[1].PositionVector.Y); if (p1 == p2 || p2 == p3 || p1 == p4 || p2 == p4 || p1 == p3 || p3 == p4) return false; // Get the segments' parameters. float dx12 = p2.X - p1.X; float dy12 = p2.Y - p1.Y; float dx34 = p4.X - p3.X; float dy34 = p4.Y - p3.Y; // Solve for t1 and t2 float denominator = (dy12 * dx34 - dx12 * dy34); float t1 = ((p1.X - p3.X) * dy34 + (p3.Y - p1.Y) * dx34) / denominator; if (float.IsInfinity(t1)) { return false; } float t2 = ((p3.X - p1.X) * dy12 + (p1.Y - p3.Y) * dx12) / -denominator; // The segments intersect if t1 and t2 are between 0 and 1. return ((t1 >= 0) && (t1 <= 1) && (t2 >= 0) && (t2 <= 1)); }
// Calculates the length of the diagonal of the axis-aligned bounding box of a set of vertices private static double boundingBoxDiagonal(Vertex[] vertices) { double[] xPositions = vertices.Select(v => v.PositionVector.X).ToArray(); double[] yPositions = vertices.Select(v => v.PositionVector.Y).ToArray(); double minX = xPositions.Min(); double maxX = xPositions.Max(); double minY = yPositions.Min(); double maxY = yPositions.Max(); double deltaX = maxX - minX; double deltaY = maxY - minY; return Math.Sqrt(deltaX * deltaX + deltaY * deltaY); }
// Calculates the dispersion of the vertex densities (the amount of vertices in a fixed radius around the vertex) // for each vertex using the coefficient of variation (standard deviation / median). // This is usually a value between 0 and 1, though it can be up to sqrt(n - 1) with n the size of the data set public static double vertexDensityDispersion(Vertex[] vertices) { double radius = 0.2d * boundingBoxDiagonal(vertices) / 2d; double[] vertexCounts = new double[vertices.Length]; for (int i = 0; i < vertexCounts.Length; i++ ) foreach (Vertex w in vertices) if (Math.Abs(Vertex.VectorBetween(vertices[i], w).Length) <= radius) vertexCounts[i]++; return standardDeviation(vertexCounts) / vertexCounts.Average(); }
// Check whether 2 nodes are connected public bool ConnectedWith(Vertex otherVertex) { return this.connectedVertexIDs.Contains(otherVertex.ID); }
// Converts a list of strings representing a graph as generated by GraphToStrings // back to a Vertex array // Helper function for LoadGraphs private static Vertex[] StringsToGraph(List<string> lines) { Vertex[] graph = new Vertex[lines.Count]; string[] fields, connectionStrings; HashSet<int> connections; int id; Vector position; for (int i = 0; i < lines.Count; i++) { fields = lines[i].Split(' '); connectionStrings = fields[3].Split(','); id = Convert.ToInt32(fields[0]); position = new Vector(Convert.ToDouble(fields[1]), Convert.ToDouble(fields[2])); connections = new HashSet<int>(); foreach (string s in connectionStrings) connections.Add(Convert.ToInt32(s)); graph[i] = new Vertex(id, position, connections); } return graph; }