topologicpy 0.7.90__py3-none-any.whl → 0.7.92__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- topologicpy/Face.py +128 -6
- topologicpy/Graph.py +557 -152
- topologicpy/Plotly.py +43 -22
- topologicpy/Vector.py +58 -42
- topologicpy/version.py +1 -1
- {topologicpy-0.7.90.dist-info → topologicpy-0.7.92.dist-info}/METADATA +1 -1
- {topologicpy-0.7.90.dist-info → topologicpy-0.7.92.dist-info}/RECORD +10 -10
- {topologicpy-0.7.90.dist-info → topologicpy-0.7.92.dist-info}/LICENSE +0 -0
- {topologicpy-0.7.90.dist-info → topologicpy-0.7.92.dist-info}/WHEEL +0 -0
- {topologicpy-0.7.90.dist-info → topologicpy-0.7.92.dist-info}/top_level.txt +0 -0
topologicpy/Graph.py
CHANGED
@@ -264,7 +264,7 @@ class _DrawTree(object):
|
|
264
264
|
return self.__str__()
|
265
265
|
|
266
266
|
class Graph:
|
267
|
-
def AdjacencyDictionary(graph, vertexLabelKey: str =
|
267
|
+
def AdjacencyDictionary(graph, vertexLabelKey: str = None, edgeKey: str = "Length", includeWeights: bool = False, reverse: bool = False, mantissa: int = 6):
|
268
268
|
"""
|
269
269
|
Returns the adjacency dictionary of the input Graph.
|
270
270
|
|
@@ -274,7 +274,7 @@ class Graph:
|
|
274
274
|
The input graph.
|
275
275
|
vertexLabelKey : str , optional
|
276
276
|
The returned vertices are labelled according to the dictionary values stored under this key.
|
277
|
-
If the vertexLabelKey does not exist, it will be created and the vertices are labelled numerically and stored in the vertex dictionary under this key. The default is
|
277
|
+
If the vertexLabelKey does not exist, it will be created and the vertices are labelled numerically and stored in the vertex dictionary under this key. The default is None.
|
278
278
|
edgeKey : str , optional
|
279
279
|
If set, the edges' dictionaries will be searched for this key to set their weight. If the key is set to "length" (case insensitive), the length of the edge will be used as its weight. If set to None, a weight of 1 will be used. The default is "Length".
|
280
280
|
includeWeights : bool , optional
|
@@ -298,6 +298,8 @@ class Graph:
|
|
298
298
|
if not Topology.IsInstance(graph, "Graph"):
|
299
299
|
print("Graph.AdjacencyDictionary - Error: The input graph is not a valid graph. Returning None.")
|
300
300
|
return None
|
301
|
+
if vertexLabelKey == None:
|
302
|
+
vertexLabelKey = "__label__"
|
301
303
|
if not isinstance(vertexLabelKey, str):
|
302
304
|
print("Graph.AdjacencyDictionary - Error: The input vertexLabelKey is not a valid string. Returning None.")
|
303
305
|
return None
|
@@ -353,6 +355,12 @@ class Graph:
|
|
353
355
|
temp_list.append(adjLabel)
|
354
356
|
temp_list.sort()
|
355
357
|
adjDict[vertex_label] = temp_list
|
358
|
+
if vertexLabelKey == "__label__": # This is label we added, so remove it
|
359
|
+
vertices = Graph.Vertices(graph)
|
360
|
+
for v in vertices:
|
361
|
+
d = Topology.Dictionary(v)
|
362
|
+
d = Dictionary.RemoveKey(d, vertexLabelKey)
|
363
|
+
v = Topology.SetDictionary(v, d)
|
356
364
|
return adjDict
|
357
365
|
|
358
366
|
@staticmethod
|
@@ -1328,22 +1336,16 @@ class Graph:
|
|
1328
1336
|
return bot_graph.serialize(format=format)
|
1329
1337
|
|
1330
1338
|
@staticmethod
|
1331
|
-
def
|
1339
|
+
def BetweennessCentrality(graph, key: str = "betweenness_centrality", mantissa: int = 6, tolerance: float = 0.001, silent: bool = False):
|
1332
1340
|
"""
|
1333
|
-
Returns the
|
1341
|
+
Returns the betweenness centrality measure of the input list of vertices within the input graph. The order of the returned list is the same as the order of the input list of vertices. If no vertices are specified, the betweeness centrality of all the vertices in the input graph is computed. See https://en.wikipedia.org/wiki/Betweenness_centrality.
|
1334
1342
|
|
1335
1343
|
Parameters
|
1336
1344
|
----------
|
1337
1345
|
graph : topologic_core.Graph
|
1338
1346
|
The input graph.
|
1339
|
-
vertices : list , optional
|
1340
|
-
The input list of vertices. The default is None which means all vertices in the input graph are considered.
|
1341
|
-
sources : list , optional
|
1342
|
-
The input list of source vertices. The default is None which means all vertices in the input graph are considered.
|
1343
|
-
destinations : list , optional
|
1344
|
-
The input list of destination vertices. The default is None which means all vertices in the input graph are considered.
|
1345
1347
|
key : str , optional
|
1346
|
-
The dictionary key under which to save the betweeness centrality score. The default is "
|
1348
|
+
The dictionary key under which to save the betweeness centrality score. The default is "betweenness_centrality".
|
1347
1349
|
mantissa : int , optional
|
1348
1350
|
The desired length of the mantissa. The default is 6.
|
1349
1351
|
tolerance : float , optional
|
@@ -1352,88 +1354,79 @@ class Graph:
|
|
1352
1354
|
Returns
|
1353
1355
|
-------
|
1354
1356
|
list
|
1355
|
-
The
|
1357
|
+
The betweenness centrality of the input list of vertices within the input graph. The values are in the range 0 to 1.
|
1356
1358
|
|
1357
1359
|
"""
|
1358
|
-
|
1360
|
+
def bfs_paths(source):
|
1361
|
+
queue = [(source, [source])]
|
1362
|
+
while queue:
|
1363
|
+
(vertex, path) = queue.pop(0)
|
1364
|
+
for next in set(py_graph[vertex]) - set(path):
|
1365
|
+
queue.append((next, path + [next]))
|
1366
|
+
yield path + [next]
|
1367
|
+
|
1368
|
+
def shortest_paths_count(source):
|
1369
|
+
paths = list(bfs_paths(source))
|
1370
|
+
shortest_paths = {v: [] for v in py_graph}
|
1371
|
+
for path in paths:
|
1372
|
+
shortest_paths[path[-1]].append(path)
|
1373
|
+
return shortest_paths
|
1374
|
+
|
1375
|
+
def calculate_betweenness():
|
1376
|
+
betweenness = {v: 0.0 for v in py_graph}
|
1377
|
+
for s in py_graph:
|
1378
|
+
shortest_paths = shortest_paths_count(s)
|
1379
|
+
dependency = {v: 0.0 for v in py_graph}
|
1380
|
+
for t in py_graph:
|
1381
|
+
if t != s:
|
1382
|
+
for path in shortest_paths[t]:
|
1383
|
+
for v in path[1:-1]:
|
1384
|
+
dependency[v] += 1.0 / len(shortest_paths[t])
|
1385
|
+
for v in py_graph:
|
1386
|
+
if v != s:
|
1387
|
+
betweenness[v] += dependency[v]
|
1388
|
+
return betweenness
|
1389
|
+
|
1359
1390
|
from topologicpy.Topology import Topology
|
1360
1391
|
from topologicpy.Dictionary import Dictionary
|
1361
1392
|
|
1362
|
-
def betweeness(vertices, topologies, tolerance=0.001):
|
1363
|
-
returnList = [0] * len(vertices)
|
1364
|
-
for topology in topologies:
|
1365
|
-
t_vertices = Topology.Vertices(topology)
|
1366
|
-
for t_v in t_vertices:
|
1367
|
-
index = Vertex.Index(t_v, vertices, strict=False, tolerance=tolerance)
|
1368
|
-
if not index == None:
|
1369
|
-
returnList[index] = returnList[index]+1
|
1370
|
-
return returnList
|
1371
|
-
|
1372
1393
|
if not Topology.IsInstance(graph, "Graph"):
|
1373
|
-
|
1394
|
+
if not silent:
|
1395
|
+
print("Graph.BetweenessCentrality - Error: The input graph is not a valid graph. Returning None.")
|
1374
1396
|
return None
|
1375
|
-
graphVertices = Graph.Vertices(graph)
|
1376
1397
|
|
1377
|
-
|
1378
|
-
|
1379
|
-
else:
|
1380
|
-
vertices = [v for v in vertices if Topology.IsInstance(v, "Vertex")]
|
1398
|
+
vertices = Graph.Vertices(graph)
|
1399
|
+
|
1381
1400
|
if len(vertices) < 1:
|
1382
|
-
|
1383
|
-
|
1384
|
-
if not isinstance(sources, list):
|
1385
|
-
sources = graphVertices
|
1386
|
-
else:
|
1387
|
-
sources = [v for v in sources if Topology.IsInstance(v, "Vertex")]
|
1388
|
-
if len(sources) < 1:
|
1389
|
-
print("Graph.BetweenessCentrality - Error: The input list of sources does not contain valid vertices. Returning None.")
|
1401
|
+
if not silent:
|
1402
|
+
print("Graph.BetweenessCentrality - Error: The input graph does not contain valid vertices. Returning None.")
|
1390
1403
|
return None
|
1391
|
-
if
|
1392
|
-
|
1393
|
-
|
1394
|
-
|
1395
|
-
|
1396
|
-
|
1397
|
-
|
1398
|
-
|
1399
|
-
|
1400
|
-
|
1401
|
-
|
1402
|
-
|
1403
|
-
|
1404
|
-
|
1405
|
-
v
|
1406
|
-
|
1407
|
-
|
1408
|
-
|
1409
|
-
|
1410
|
-
|
1411
|
-
|
1412
|
-
|
1413
|
-
|
1414
|
-
|
1415
|
-
|
1416
|
-
|
1417
|
-
except:
|
1418
|
-
for so in sources:
|
1419
|
-
v1 = Graph.NearestVertex(graph, so)
|
1420
|
-
for si in destinations:
|
1421
|
-
v2 = Graph.NearestVertex(graph, si)
|
1422
|
-
if not v1 == v2:
|
1423
|
-
path = Graph.ShortestPath(graph, v1, v2)
|
1424
|
-
if path:
|
1425
|
-
paths.append(path)
|
1426
|
-
|
1427
|
-
scores = betweeness(vertices, paths, tolerance=tolerance)
|
1428
|
-
minValue = min(scores)
|
1429
|
-
maxValue = max(scores)
|
1430
|
-
size = maxValue - minValue
|
1431
|
-
scores = [round((v-minValue)/size, mantissa) for v in scores]
|
1432
|
-
for i, v in enumerate(vertices):
|
1433
|
-
d = Topology.Dictionary(v)
|
1434
|
-
d = Dictionary.SetValueAtKey(d, key, scores[i])
|
1435
|
-
v = Topology.SetDictionary(v, d)
|
1436
|
-
return scores
|
1404
|
+
if len(vertices) == 1:
|
1405
|
+
d = Topology.Dictionary(vertices[0])
|
1406
|
+
d = Dictionary.SetValueAtKey(d, key, 1.0)
|
1407
|
+
vertices[0] = Topology.SetDictionary(vertices[0], d)
|
1408
|
+
return [1.0]
|
1409
|
+
|
1410
|
+
py_graph = Graph.AdjacencyDictionary(g)
|
1411
|
+
betweenness = calculate_betweenness()
|
1412
|
+
for v in betweenness:
|
1413
|
+
betweenness[v] /= 2.0 # Each shortest path is counted twice
|
1414
|
+
|
1415
|
+
max_betweenness = max(betweenness.values())
|
1416
|
+
if max_betweenness > 0:
|
1417
|
+
for v in betweenness:
|
1418
|
+
betweenness[v] /= max_betweenness # Normalize to [0, 1]
|
1419
|
+
|
1420
|
+
|
1421
|
+
return_betweenness = [0]*len(vertices)
|
1422
|
+
for i, v in betweenness.items():
|
1423
|
+
vertex = vertices[int(i)]
|
1424
|
+
d = Topology.Dictionary(vertex)
|
1425
|
+
d = Dictionary.SetValueAtKey(d, key, round(v, mantissa))
|
1426
|
+
vertex = Topology.SetDictionary(vertex, d)
|
1427
|
+
return_betweenness[int(i)] = v
|
1428
|
+
|
1429
|
+
return return_betweenness
|
1437
1430
|
|
1438
1431
|
@staticmethod
|
1439
1432
|
def ByAdjacencyMatrixCSVPath(path: str, dictionaries: list = None, silent: bool = False):
|
@@ -1465,17 +1458,21 @@ class Graph:
|
|
1465
1458
|
return Graph.ByAdjacencyMatrix(adjacencyMatrix=adjacency_matrix, dictionaries=dictionaries, silent=silent)
|
1466
1459
|
|
1467
1460
|
@staticmethod
|
1468
|
-
def ByAdjacencyMatrix(adjacencyMatrix, dictionaries = None, xMin=-0.5, yMin=-0.5, zMin=-0.5, xMax=0.5, yMax=0.5, zMax=0.5, silent=False):
|
1461
|
+
def ByAdjacencyMatrix(adjacencyMatrix, dictionaries = None, edgeKeyFwd="weightFwd", edgeKeyBwd="weightBwd", xMin=-0.5, yMin=-0.5, zMin=-0.5, xMax=0.5, yMax=0.5, zMax=0.5, silent=False):
|
1469
1462
|
"""
|
1470
1463
|
Returns graphs according to the input folder path. This method assumes the CSV files follow DGL's schema.
|
1471
1464
|
|
1472
1465
|
Parameters
|
1473
1466
|
----------
|
1474
1467
|
adjacencyMatrix : list
|
1475
|
-
The adjacency matrix expressed as a nested list of 0s and
|
1468
|
+
The adjacency matrix expressed as a nested list of 0s and a number not 0 which represents the edge weight.
|
1476
1469
|
dictionaries : list , optional
|
1477
1470
|
A list of dictionaries to assign to the vertices of the graph. This list should be in
|
1478
1471
|
the same order and of the same length as the rows in the adjacency matrix.
|
1472
|
+
edgeKeyFwd : str , optional
|
1473
|
+
The dictionary key under which to store the edge weight value for forward edge. The default is "weight".
|
1474
|
+
edgeKeyBwd : str , optional
|
1475
|
+
The dictionary key under which to store the edge weight value for backward edge. The default is "weight".
|
1479
1476
|
xMin : float , optional
|
1480
1477
|
The desired minimum value to assign for a vertex's X coordinate. The default is -0.5.
|
1481
1478
|
yMin : float , optional
|
@@ -1500,6 +1497,7 @@ class Graph:
|
|
1500
1497
|
from topologicpy.Vertex import Vertex
|
1501
1498
|
from topologicpy.Edge import Edge
|
1502
1499
|
from topologicpy.Topology import Topology
|
1500
|
+
from topologicpy.Dictionary import Dictionary
|
1503
1501
|
import random
|
1504
1502
|
|
1505
1503
|
if not isinstance(adjacencyMatrix, list):
|
@@ -1526,11 +1524,17 @@ class Graph:
|
|
1526
1524
|
|
1527
1525
|
# Add edges based on the adjacency matrix
|
1528
1526
|
edges = []
|
1527
|
+
visited = []
|
1529
1528
|
for i in range(len(adjacencyMatrix)):
|
1530
1529
|
for j in range(len(adjacencyMatrix)):
|
1531
1530
|
if not i == j:
|
1532
|
-
if
|
1533
|
-
|
1531
|
+
if (adjacencyMatrix[i][j] != 0 or adjacencyMatrix[j][i] != 0) and not (i,j) in visited:
|
1532
|
+
edge = Edge.ByVertices([vertices[i], vertices[j]]) # Create only one edge
|
1533
|
+
d = Dictionary.ByKeysValues([edgeKeyFwd, edgeKeyBwd], [adjacencyMatrix[i][j], adjacencyMatrix[j][i]])
|
1534
|
+
edge = Topology.SetDictionary(edge, d)
|
1535
|
+
edges.append(edge)
|
1536
|
+
visited.append((i,j))
|
1537
|
+
visited.append((j,i))
|
1534
1538
|
|
1535
1539
|
return Graph.ByVerticesEdges(vertices, edges)
|
1536
1540
|
|
@@ -2729,7 +2733,82 @@ class Graph:
|
|
2729
2733
|
g_e = Topology.SetDictionary(g_e, d)
|
2730
2734
|
g_edges.append(g_e)
|
2731
2735
|
return Graph.ByVerticesEdges(g_vertices, g_edges)
|
2732
|
-
|
2736
|
+
|
2737
|
+
@staticmethod
|
2738
|
+
def ByNetworkXGraph(nxGraph, xKey="x", yKey="y", zKey="z", range=(-1, 1), mantissa: int = 6, tolerance: float = 0.0001):
|
2739
|
+
"""
|
2740
|
+
Converts the input NetworkX graph into a topologic Graph. See http://networkx.org
|
2741
|
+
|
2742
|
+
Parameters
|
2743
|
+
----------
|
2744
|
+
nxGraph : NetworkX graph
|
2745
|
+
The input NetworkX graph.
|
2746
|
+
xKey : str , optional
|
2747
|
+
The dictionary key under which to find the X-Coordinate of the vertex. The default is 'x'.
|
2748
|
+
yKey : str , optional
|
2749
|
+
The dictionary key under which to find the Y-Coordinate of the vertex. The default is 'y'.
|
2750
|
+
zKey : str , optional
|
2751
|
+
The dictionary key under which to find the Z-Coordinate of the vertex. The default is 'z'.
|
2752
|
+
range : tuple , optional
|
2753
|
+
The range to use for position coordinates if no values are found in the dictionaries. The default is (-1,1)
|
2754
|
+
mantissa : int , optional
|
2755
|
+
The desired length of the mantissa. The default is 6.
|
2756
|
+
tolerance : float , optional
|
2757
|
+
The desired tolerance. The default is 0.0001.
|
2758
|
+
|
2759
|
+
Returns
|
2760
|
+
-------
|
2761
|
+
topologicpy.Graph
|
2762
|
+
The created topologic graph.
|
2763
|
+
|
2764
|
+
"""
|
2765
|
+
from topologicpy.Vertex import Vertex
|
2766
|
+
from topologicpy.Edge import Edge
|
2767
|
+
from topologicpy.Topology import Topology
|
2768
|
+
from topologicpy.Dictionary import Dictionary
|
2769
|
+
|
2770
|
+
import random
|
2771
|
+
import numpy as np
|
2772
|
+
|
2773
|
+
# Create a mapping from NetworkX nodes to TopologicPy vertices
|
2774
|
+
nx_to_topologic_vertex = {}
|
2775
|
+
|
2776
|
+
# Create TopologicPy vertices for each node in the NetworkX graph
|
2777
|
+
vertices = []
|
2778
|
+
for node, data in nxGraph.nodes(data=True):
|
2779
|
+
# Attempt to get X, Y, Z from the node data
|
2780
|
+
x = round(data.get(xKey, random.uniform(*range)), mantissa)
|
2781
|
+
y = round(data.get(yKey, random.uniform(*range)), mantissa)
|
2782
|
+
z = round(data.get(zKey, 0), mantissa) # If there are no Z values, this is probably a flat graph.
|
2783
|
+
# Create a TopologicPy vertex with the node data dictionary
|
2784
|
+
vertex = Vertex.ByCoordinates(x,y,z)
|
2785
|
+
cleaned_values = []
|
2786
|
+
for value in data.values():
|
2787
|
+
if isinstance(value, np.ndarray):
|
2788
|
+
value = list(value)
|
2789
|
+
cleaned_values.append(value)
|
2790
|
+
|
2791
|
+
node_dict = Dictionary.ByKeysValues(list(data.keys()), cleaned_values)
|
2792
|
+
vertex = Topology.SetDictionary(vertex, node_dict)
|
2793
|
+
nx_to_topologic_vertex[node] = vertex
|
2794
|
+
vertices.append(vertex)
|
2795
|
+
|
2796
|
+
# Create TopologicPy edges for each edge in the NetworkX graph
|
2797
|
+
edges = []
|
2798
|
+
for u, v, data in nxGraph.edges(data=True):
|
2799
|
+
start_vertex = nx_to_topologic_vertex[u]
|
2800
|
+
end_vertex = nx_to_topologic_vertex[v]
|
2801
|
+
|
2802
|
+
# Create a TopologicPy edge with the edge data dictionary
|
2803
|
+
edge_dict = Dictionary.ByKeysValues(list(data.keys()), list(data.values()))
|
2804
|
+
edge = Edge.ByVertices([start_vertex, end_vertex], tolerance=tolerance)
|
2805
|
+
edge = Topology.SetDictionary(edge, edge_dict)
|
2806
|
+
edges.append(edge)
|
2807
|
+
|
2808
|
+
# Create and return the TopologicPy graph
|
2809
|
+
topologic_graph = Graph.ByVerticesEdges(vertices, edges)
|
2810
|
+
return topologic_graph
|
2811
|
+
|
2733
2812
|
@staticmethod
|
2734
2813
|
def ByTopology(topology,
|
2735
2814
|
direct: bool = True,
|
@@ -4102,6 +4181,48 @@ class Graph:
|
|
4102
4181
|
v = Topology.SetDictionary(v, d)
|
4103
4182
|
return graph
|
4104
4183
|
|
4184
|
+
@staticmethod
|
4185
|
+
def Complete(graph, silent: bool = False):
|
4186
|
+
"""
|
4187
|
+
Completes the graph by conneting unconnected vertices.
|
4188
|
+
|
4189
|
+
Parameters
|
4190
|
+
----------
|
4191
|
+
graph : topologic_core.Graph
|
4192
|
+
The input graph.
|
4193
|
+
tolerance : float , optional
|
4194
|
+
The desired tolerance. The default is 0.0001.
|
4195
|
+
silent : bool , optional
|
4196
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
4197
|
+
|
4198
|
+
Returns
|
4199
|
+
-------
|
4200
|
+
topologicpy.Graph
|
4201
|
+
the completed graph
|
4202
|
+
"""
|
4203
|
+
from topologicpy.Edge import Edge
|
4204
|
+
from topologicpy.Topology import Topology
|
4205
|
+
|
4206
|
+
if not Topology.IsInstance(graph, "Graph"):
|
4207
|
+
if not silent:
|
4208
|
+
print("Graph.ConnectedComponents - Error: The input graph is not a valid graph. Returning None.")
|
4209
|
+
return None
|
4210
|
+
|
4211
|
+
vertices = Graph.Vertices(graph)
|
4212
|
+
edges = Graph.Edges(graph)
|
4213
|
+
visited = set()
|
4214
|
+
new_edges = []
|
4215
|
+
for sv in vertices:
|
4216
|
+
for ev in vertices:
|
4217
|
+
if sv != ev and not (sv, ev) in visited:
|
4218
|
+
visited.add((sv, ev))
|
4219
|
+
visited.add((ev,sv))
|
4220
|
+
edge = Graph.Edge(graph, sv, ev)
|
4221
|
+
if edge == None:
|
4222
|
+
new_edges.append(Edge.ByVertices(sv, ev))
|
4223
|
+
edges += new_edges
|
4224
|
+
return Graph.ByVerticesEdges(vertices, edges)
|
4225
|
+
|
4105
4226
|
@staticmethod
|
4106
4227
|
def ConnectedComponents(graph, tolerance: float = 0.0001, silent: bool = False):
|
4107
4228
|
"""
|
@@ -4366,6 +4487,68 @@ class Graph:
|
|
4366
4487
|
return_centralities.append(centralities[i])
|
4367
4488
|
return centralities
|
4368
4489
|
|
4490
|
+
@staticmethod
|
4491
|
+
def Community(graph, key: str = "community", mantissa: int = 6, tolerance: float = 0.0001, silent: bool = False):
|
4492
|
+
"""
|
4493
|
+
Computes the best community partition of the input graph based on the Louvain method. See https://en.wikipedia.org/wiki/Louvain_method.
|
4494
|
+
This method depends on NetworkX and the python-louvain libraries
|
4495
|
+
|
4496
|
+
Parameters
|
4497
|
+
----------
|
4498
|
+
graph : topologicp.Graph
|
4499
|
+
The input topologic graph.
|
4500
|
+
key : str , optional
|
4501
|
+
The dictionary key under which to save the closeness centrality score. The default is "community".
|
4502
|
+
mantissa : int , optional
|
4503
|
+
The desired length of the mantissa. The default is 6.
|
4504
|
+
tolerance : float , optional
|
4505
|
+
The desired tolerance. The default is 0.0001.
|
4506
|
+
silent : bool , optional
|
4507
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
4508
|
+
Returns
|
4509
|
+
-------
|
4510
|
+
topologicpy.Graph
|
4511
|
+
The created topologic graph.
|
4512
|
+
|
4513
|
+
"""
|
4514
|
+
from topologicpy.Topology import Topology
|
4515
|
+
from topologicpy.Dictionary import Dictionary
|
4516
|
+
import os
|
4517
|
+
import warnings
|
4518
|
+
|
4519
|
+
try:
|
4520
|
+
import community as community_louvain
|
4521
|
+
except:
|
4522
|
+
print("Graph.Community - Installing required pyhon-louvain library.")
|
4523
|
+
try:
|
4524
|
+
os.system("pip install python-louvain")
|
4525
|
+
except:
|
4526
|
+
os.system("pip install python-louvain --user")
|
4527
|
+
try:
|
4528
|
+
import community as community_louvain
|
4529
|
+
print("Graph.Community - python-louvain library installed correctly.")
|
4530
|
+
except:
|
4531
|
+
warnings.warn("Graph.Community - Error: Could not import python-louvain. Please install manually.")
|
4532
|
+
|
4533
|
+
if not Topology.IsInstance(graph, "graph"):
|
4534
|
+
if not silent:
|
4535
|
+
print("Graph.Community - Error: The input graph parameter is not a valid topologic graph. Returning None")
|
4536
|
+
return None
|
4537
|
+
|
4538
|
+
vertices = Graph.Vertices(graph)
|
4539
|
+
nx_graph = Graph.NetworkXGraph(graph, mantissa=mantissa, tolerance=tolerance)
|
4540
|
+
# Apply the Louvain algorithm
|
4541
|
+
partition = community_louvain.best_partition(nx_graph)
|
4542
|
+
communities = []
|
4543
|
+
# Add the partition value to each node's properties
|
4544
|
+
for node, community_id in partition.items():
|
4545
|
+
nx_graph.nodes[node][key] = community_id
|
4546
|
+
d = Topology.Dictionary(vertices[node])
|
4547
|
+
d = Dictionary.SetValueAtKey(d, key, community_id)
|
4548
|
+
vertices[node] = Topology.SetDictionary(vertices[node], d)
|
4549
|
+
communities.append(community_id)
|
4550
|
+
return communities
|
4551
|
+
|
4369
4552
|
@staticmethod
|
4370
4553
|
def Connect(graph, verticesA, verticesB, tolerance=0.0001):
|
4371
4554
|
"""
|
@@ -4413,6 +4596,53 @@ class Graph:
|
|
4413
4596
|
_ = graph.Connect(verticesA, verticesB, tolerance) # Hook to Core
|
4414
4597
|
return graph
|
4415
4598
|
|
4599
|
+
@staticmethod
|
4600
|
+
def Connectivity(graph, vertices=None, key: str = "connectivity", edgeKey: str = None, tolerance = 0.0001, silent = False):
|
4601
|
+
"""
|
4602
|
+
Return the connectivity measure of the input list of vertices within the input graph. The order of the returned list is the same as the order of the input list of vertices. If no vertices are specified, the connectivity of all the vertices in the input graph is computed. See https://www.spacesyntax.online/term/connectivity/.
|
4603
|
+
|
4604
|
+
Parameters
|
4605
|
+
----------
|
4606
|
+
graph : topologic_core.Graph
|
4607
|
+
The input graph.
|
4608
|
+
vertices : list , optional
|
4609
|
+
The input list of vertices. The default is None.
|
4610
|
+
key : str , optional
|
4611
|
+
The dictionary key under which to save the connectivity score. The default is "connectivity".
|
4612
|
+
edgeKey : str , optional
|
4613
|
+
If specified, the value in the connected edges' dictionary specified by the edgeKey string will be aggregated to calculate
|
4614
|
+
the vertex degree. If a numeric value cannot be retrieved from an edge, a value of 1 is used instead. This is used in weighted graphs.
|
4615
|
+
tolerance : float , optional
|
4616
|
+
The desired tolerance. The default is 0.0001.
|
4617
|
+
|
4618
|
+
tolerance : float , optional
|
4619
|
+
The desired tolerance. The default is 0.0001.
|
4620
|
+
silent : bool , optional
|
4621
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
4622
|
+
|
4623
|
+
Returns
|
4624
|
+
-------
|
4625
|
+
list
|
4626
|
+
The closeness centrality of the input list of vertices within the input graph. The values are in the range 0 to 1.
|
4627
|
+
|
4628
|
+
"""
|
4629
|
+
|
4630
|
+
from topologicpy.Topology import Topology
|
4631
|
+
from topologicpy.Dictionary import Dictionary
|
4632
|
+
|
4633
|
+
if not Topology.IsInstance(graph, "Graph"):
|
4634
|
+
if not silent:
|
4635
|
+
print("Graph.ClosenessCentrality - Error: The input graph is not a valid graph. Returning None.")
|
4636
|
+
return None
|
4637
|
+
if vertices == None:
|
4638
|
+
vertices = Graph.Vertices(graph)
|
4639
|
+
connectivities = [Graph.VertexDegree(graph, v, edgeKey=edgeKey, tolerance=tolerance, silent=silent) for v in vertices]
|
4640
|
+
for i, v in enumerate(vertices):
|
4641
|
+
d = Topology.Dictionary(v)
|
4642
|
+
d = Dictionary.SetValueAtKey(d, key, connectivities[i])
|
4643
|
+
v = Topology.SetDictionary(v, d)
|
4644
|
+
return connectivities
|
4645
|
+
|
4416
4646
|
@staticmethod
|
4417
4647
|
def ContainsEdge(graph, edge, tolerance=0.0001):
|
4418
4648
|
"""
|
@@ -5898,11 +6128,12 @@ class Graph:
|
|
5898
6128
|
|
5899
6129
|
@staticmethod
|
5900
6130
|
def Reshape(graph,
|
5901
|
-
shape="
|
6131
|
+
shape="spring 2D",
|
5902
6132
|
k=0.8, seed=None,
|
5903
6133
|
iterations=50,
|
5904
6134
|
rootVertex=None,
|
5905
6135
|
size=1,
|
6136
|
+
factor=1,
|
5906
6137
|
sides=16,
|
5907
6138
|
key="",
|
5908
6139
|
tolerance=0.0001,
|
@@ -5916,16 +6147,17 @@ class Graph:
|
|
5916
6147
|
The input graph.
|
5917
6148
|
shape : str , optional
|
5918
6149
|
The desired shape of the graph.
|
5919
|
-
|
5920
|
-
If set to '
|
5921
|
-
If set to '
|
5922
|
-
If set to '
|
5923
|
-
If set to '
|
5924
|
-
If set to '
|
5925
|
-
If set to '
|
5926
|
-
If set to '
|
5927
|
-
If set to '
|
5928
|
-
|
6150
|
+
['circle 2D', 'grid 2D', 'line 2D', 'radial 2D', 'spring 2D', 'tree 2D', 'grid 3D', 'sphere 3D', 'tree 3D']
|
6151
|
+
If set to 'spring 2D' or 'spring_3d', the algorithm uses a simplified version of the Fruchterman-Reingold force-directed algorithm to distribute the vertices.
|
6152
|
+
If set to 'radial 2D', the nodes will be distributed along concentric circles in the XY plane.
|
6153
|
+
If set to 'tree 2D' or 'tree 3D', the nodes will be distributed using the Reingold-Tillford layout.
|
6154
|
+
If set to 'circle 2D', the nodes will be distributed on the cirumference of a segemented circles in the XY plane, based on the size and sides input parameter (radius=size/2).
|
6155
|
+
If set to 'line 2D', the nodes will be distributed on a line in the XY plane based on the size input parameter (length=size).
|
6156
|
+
If set to 'spehere 3D', the nodes will be distributed on the surface of a sphere based on the size input parameter raidus=size/2).
|
6157
|
+
If set to 'grid 2D', the nodes will be distributed on a grid in the XY plane with size based on the size input parameter (length=width=size).
|
6158
|
+
If set to 'grid 3D', the nodes will be distributed on a 3D cubic grid/matrix based on the size input parameter(width=length=height=size).
|
6159
|
+
If set to 'cluster 2D', or 'cluster 3D, the nodes will be clustered according to the 'key' input parameter. The overall radius of the cluster is determined by the size input parameter (radius = size/2)
|
6160
|
+
The default is 'spring 2D'.
|
5929
6161
|
k : float, optional
|
5930
6162
|
The desired spring constant to use for the attractive and repulsive forces. The default is 0.8.
|
5931
6163
|
seed : int , optional
|
@@ -5934,6 +6166,8 @@ class Graph:
|
|
5934
6166
|
The desired maximum number of iterations to solve the forces in the 'spring' mode. The default is 50.
|
5935
6167
|
rootVertex : topologic_core.Vertex , optional
|
5936
6168
|
The desired vertex to use as the root of the tree and radial layouts.
|
6169
|
+
size : float , optional
|
6170
|
+
The desired overall size of the graph.
|
5937
6171
|
sides : int , optional
|
5938
6172
|
The desired number of sides of the circle layout option. The default is 16
|
5939
6173
|
length : float, optional
|
@@ -6163,7 +6397,7 @@ class Graph:
|
|
6163
6397
|
|
6164
6398
|
for i, c_v in enumerate(c_vertices):
|
6165
6399
|
d = Topology.Dictionary(vertices[i])
|
6166
|
-
c_v = Topology.SetDictionary(c_v, d)
|
6400
|
+
c_v = Topology.SetDictionary(c_v, d, silent=True)
|
6167
6401
|
adj_dict = Graph.AdjacencyDictionary(graph)
|
6168
6402
|
keys = adj_dict.keys()
|
6169
6403
|
|
@@ -6185,7 +6419,7 @@ class Graph:
|
|
6185
6419
|
orig_edge_index = edge_dict.get(str(x)+"_"+str(y), edge_dict.get(str(y)+"_"+str(x), None))
|
6186
6420
|
if orig_edge_index:
|
6187
6421
|
d = Topology.Dictionary(edges[orig_edge_index])
|
6188
|
-
e = Topology.SetDictionary(e, d)
|
6422
|
+
e = Topology.SetDictionary(e, d, silent=True)
|
6189
6423
|
c_edges.append(e)
|
6190
6424
|
used[x][y] = 1
|
6191
6425
|
used[y][x] = 1
|
@@ -6355,7 +6589,7 @@ class Graph:
|
|
6355
6589
|
c_vertices = [Vertex.ByCoordinates(coord) for coord in c_points]
|
6356
6590
|
for i, c_v in enumerate(c_vertices):
|
6357
6591
|
d = Topology.Dictionary(vertices[i])
|
6358
|
-
c_v = Topology.SetDictionary(c_v, d)
|
6592
|
+
c_v = Topology.SetDictionary(c_v, d, silent=True)
|
6359
6593
|
adj_dict = Graph.AdjacencyDictionary(graph)
|
6360
6594
|
keys = adj_dict.keys()
|
6361
6595
|
|
@@ -6377,7 +6611,7 @@ class Graph:
|
|
6377
6611
|
orig_edge_index = edge_dict.get(str(x)+"_"+str(y), edge_dict.get(str(y)+"_"+str(x), None))
|
6378
6612
|
if orig_edge_index:
|
6379
6613
|
d = Topology.Dictionary(edges[orig_edge_index])
|
6380
|
-
e = Topology.SetDictionary(e, d)
|
6614
|
+
e = Topology.SetDictionary(e, d, silent=True)
|
6381
6615
|
c_edges.append(e)
|
6382
6616
|
used[x][y] = 1
|
6383
6617
|
used[y][x] = 1
|
@@ -6407,7 +6641,7 @@ class Graph:
|
|
6407
6641
|
|
6408
6642
|
for i, c_v in enumerate(c_vertices):
|
6409
6643
|
d = Topology.Dictionary(vertices[i])
|
6410
|
-
c_v = Topology.SetDictionary(c_v, d)
|
6644
|
+
c_v = Topology.SetDictionary(c_v, d, silent=True)
|
6411
6645
|
adj_dict = Graph.AdjacencyDictionary(graph)
|
6412
6646
|
keys = adj_dict.keys()
|
6413
6647
|
|
@@ -6422,7 +6656,7 @@ class Graph:
|
|
6422
6656
|
orig_edge_index = edge_dict.get(str(x)+"_"+str(y), edge_dict.get(str(y)+"_"+str(x), None))
|
6423
6657
|
if orig_edge_index:
|
6424
6658
|
d = Topology.Dictionary(edges[orig_edge_index])
|
6425
|
-
e = Topology.SetDictionary(e, d)
|
6659
|
+
e = Topology.SetDictionary(e, d, silent=True)
|
6426
6660
|
c_edges.append(e)
|
6427
6661
|
used[x][y] = 1
|
6428
6662
|
used[y][x] = 1
|
@@ -6448,7 +6682,7 @@ class Graph:
|
|
6448
6682
|
|
6449
6683
|
for i, c_v in enumerate(c_vertices):
|
6450
6684
|
d = Topology.Dictionary(vertices[i])
|
6451
|
-
c_v = Topology.SetDictionary(c_v, d)
|
6685
|
+
c_v = Topology.SetDictionary(c_v, d, silent=True)
|
6452
6686
|
adj_dict = Graph.AdjacencyDictionary(graph)
|
6453
6687
|
keys = adj_dict.keys()
|
6454
6688
|
c_edges = []
|
@@ -6465,7 +6699,7 @@ class Graph:
|
|
6465
6699
|
|
6466
6700
|
orig_edge_index = edge_dict[str(x)+"_"+str(y)]
|
6467
6701
|
d = Topology.Dictionary(edges[orig_edge_index])
|
6468
|
-
e = Topology.SetDictionary(e, d)
|
6702
|
+
e = Topology.SetDictionary(e, d, silent=True)
|
6469
6703
|
c_edges.append(e)
|
6470
6704
|
used[x][y] = 1
|
6471
6705
|
used[y][x] = 1
|
@@ -6490,7 +6724,7 @@ class Graph:
|
|
6490
6724
|
|
6491
6725
|
for i, c_v in enumerate(c_vertices):
|
6492
6726
|
d = Topology.Dictionary(vertices[i])
|
6493
|
-
c_v = Topology.SetDictionary(c_v, d)
|
6727
|
+
c_v = Topology.SetDictionary(c_v, d, silent=True)
|
6494
6728
|
adj_dict = Graph.AdjacencyDictionary(graph)
|
6495
6729
|
keys = adj_dict.keys()
|
6496
6730
|
|
@@ -6505,7 +6739,7 @@ class Graph:
|
|
6505
6739
|
orig_edge_index = edge_dict.get(str(x)+"_"+str(y), edge_dict.get(str(y)+"_"+str(x), None))
|
6506
6740
|
if orig_edge_index:
|
6507
6741
|
d = Topology.Dictionary(edges[orig_edge_index])
|
6508
|
-
e = Topology.SetDictionary(e, d)
|
6742
|
+
e = Topology.SetDictionary(e, d, silent=True)
|
6509
6743
|
c_edges.append(e)
|
6510
6744
|
used[x][y] = 1
|
6511
6745
|
used[y][x] = 1
|
@@ -6682,46 +6916,97 @@ class Graph:
|
|
6682
6916
|
|
6683
6917
|
return pos
|
6684
6918
|
|
6919
|
+
|
6920
|
+
|
6685
6921
|
def radial_layout_2d(edge_list, root_index=0):
|
6922
|
+
import numpy as np
|
6923
|
+
from collections import deque
|
6924
|
+
# Build tree and get layout from Buchheim
|
6686
6925
|
root, num_nodes = tree_from_edge_list(edge_list, root_index)
|
6687
6926
|
dt = buchheim(root)
|
6688
|
-
pos = np.zeros((num_nodes, 2))
|
6689
|
-
|
6690
|
-
pos[int(dt.tree.node), 0] = dt.x
|
6691
|
-
pos[int(dt.tree.node), 1] = dt.y
|
6692
|
-
|
6693
|
-
old_roots = [dt]
|
6694
|
-
new_roots = []
|
6695
|
-
|
6696
|
-
while(len(old_roots) > 0):
|
6697
|
-
new_roots = []
|
6698
|
-
for temp_root in old_roots:
|
6699
|
-
children = temp_root.children
|
6700
|
-
for child in children:
|
6701
|
-
pos[int(child.tree.node), 0] = child.x
|
6702
|
-
pos[int(child.tree.node), 1] = child.y
|
6703
|
-
new_roots.extend(children)
|
6704
|
-
|
6705
|
-
old_roots = new_roots
|
6706
|
-
|
6707
|
-
# pos[:, 1] = np.max(pos[:, 1]) - pos[:, 1]
|
6708
|
-
pos[:, 0] = pos[:, 0] - np.min(pos[:, 0])
|
6709
|
-
pos[:, 1] = pos[:, 1] - np.min(pos[:, 1])
|
6710
|
-
|
6711
|
-
pos[:, 0] = pos[:, 0] / np.max(pos[:, 0])
|
6712
|
-
pos[:, 0] = pos[:, 0] - pos[:, 0][root_index]
|
6713
|
-
|
6714
|
-
range_ = np.max(pos[:, 0]) - np.min(pos[:, 0])
|
6715
|
-
pos[:, 0] = pos[:, 0] / range_
|
6716
6927
|
|
6717
|
-
|
6718
|
-
pos
|
6928
|
+
# Initialize positions array
|
6929
|
+
pos = np.zeros((num_nodes, 2))
|
6930
|
+
pos[int(dt.tree.node)] = [dt.x, dt.y]
|
6719
6931
|
|
6720
|
-
|
6932
|
+
# Efficient tree traversal using a queue
|
6933
|
+
queue = deque([dt])
|
6934
|
+
while queue:
|
6935
|
+
current = queue.popleft()
|
6936
|
+
for child in current.children:
|
6937
|
+
pos[int(child.tree.node)] = [child.x, child.y]
|
6938
|
+
queue.append(child)
|
6939
|
+
|
6940
|
+
# Normalize positions
|
6941
|
+
pos[:, 0] -= np.min(pos[:, 0])
|
6942
|
+
pos[:, 1] -= np.min(pos[:, 1])
|
6943
|
+
pos[:, 0] /= np.max(pos[:, 0])
|
6944
|
+
pos[:, 1] /= np.max(pos[:, 1])
|
6945
|
+
|
6946
|
+
# Center the root and scale the x-coordinates
|
6947
|
+
pos[:, 0] -= pos[root_index, 0]
|
6948
|
+
pos[:, 0] /= (np.max(pos[:, 0]) - np.min(pos[:, 0]))
|
6949
|
+
pos[:, 0] *= np.pi * 1.98
|
6950
|
+
|
6951
|
+
# Convert to polar coordinates
|
6952
|
+
new_pos = np.zeros_like(pos)
|
6721
6953
|
new_pos[:, 0] = pos[:, 1] * np.cos(pos[:, 0])
|
6722
6954
|
new_pos[:, 1] = pos[:, 1] * np.sin(pos[:, 0])
|
6723
|
-
|
6955
|
+
|
6724
6956
|
return new_pos
|
6957
|
+
|
6958
|
+
def dendrimer_layout_2d(graph, root_index=0, base_radius=1, radius_factor=1.5):
|
6959
|
+
"""
|
6960
|
+
Given a graph as an adjacency dictionary, this function generates a dendrimer layout
|
6961
|
+
and returns positions of the nodes in 2D space.
|
6962
|
+
|
6963
|
+
:param graph: dict, adjacency dictionary where keys are node ids and values are sets/lists of neighboring nodes
|
6964
|
+
:param root_index: int, index of the node to start the layout (default: 0)
|
6965
|
+
:return: list of positions [x, y] for each node, sorted by node id
|
6966
|
+
"""
|
6967
|
+
import numpy as np
|
6968
|
+
|
6969
|
+
# Initialize variables
|
6970
|
+
positions = {}
|
6971
|
+
visited = set()
|
6972
|
+
layers = {}
|
6973
|
+
|
6974
|
+
# Helper function to perform a DFS and organize nodes in layers
|
6975
|
+
def dfs(node, depth):
|
6976
|
+
visited.add(node)
|
6977
|
+
if depth not in layers:
|
6978
|
+
layers[depth] = []
|
6979
|
+
layers[depth].append(node)
|
6980
|
+
for neighbor in graph.get(node, []):
|
6981
|
+
if neighbor not in visited:
|
6982
|
+
dfs(neighbor, depth + 1)
|
6983
|
+
|
6984
|
+
# Start DFS from the given root node
|
6985
|
+
starting_node = list(graph.keys())[root_index]
|
6986
|
+
dfs(starting_node, 0)
|
6987
|
+
|
6988
|
+
# Perform DFS for all nodes to handle disconnected components
|
6989
|
+
for node in graph.keys():
|
6990
|
+
if node not in visited:
|
6991
|
+
dfs(node, 0) # Start a new DFS for each unvisited node
|
6992
|
+
|
6993
|
+
# Compute positions based on layers
|
6994
|
+
for depth, nodes in layers.items():
|
6995
|
+
print("depth:", depth)
|
6996
|
+
# Place nodes in a circular arrangement at each layer
|
6997
|
+
num_nodes = len(nodes)
|
6998
|
+
angle_step = 2 * np.pi / num_nodes if num_nodes > 0 else 0
|
6999
|
+
for i, node in enumerate(nodes):
|
7000
|
+
angle = i * angle_step
|
7001
|
+
x = base_radius*depth*np.cos(angle)
|
7002
|
+
y = base_radius*depth*np.sin(angle)
|
7003
|
+
positions[node] = (x, y)
|
7004
|
+
|
7005
|
+
# Sort the positions by node id and return them
|
7006
|
+
keys = list(positions.keys())
|
7007
|
+
keys.sort()
|
7008
|
+
return_values = [list(positions[key]) for key in keys]
|
7009
|
+
return return_values
|
6725
7010
|
|
6726
7011
|
def spherical_layout_3d(edge_list, root_index=0):
|
6727
7012
|
root, num_nodes = tree_from_edge_list(edge_list, root_index)
|
@@ -6794,7 +7079,7 @@ class Graph:
|
|
6794
7079
|
return line_layout_2d(graph, length=size)
|
6795
7080
|
elif 'grid' in shape.lower() and '2d' in shape.lower():
|
6796
7081
|
return grid_layout_2d(graph, size=size)
|
6797
|
-
elif 'sphere'
|
7082
|
+
elif 'sphere' in shape.lower() and '3d' in shape.lower():
|
6798
7083
|
return sphere_layout_3d(graph, radius=size/2)
|
6799
7084
|
elif 'grid' in shape.lower() and '3d' in shape.lower():
|
6800
7085
|
return grid_layout_3d(graph, size=size)
|
@@ -6825,12 +7110,13 @@ class Graph:
|
|
6825
7110
|
elif 'tree' in shape.lower() and '2d' in shape.lower():
|
6826
7111
|
positions = tree_layout_2d(edges, root_index=root_index)
|
6827
7112
|
elif 'tree' in shape.lower() and '3d' in shape.lower():
|
6828
|
-
positions = tree_layout_3d(edges, root_index=root_index, base_radius=
|
7113
|
+
positions = tree_layout_3d(edges, root_index=root_index, base_radius=size/2, radius_factor=factor)
|
7114
|
+
elif 'dendrimer' in shape.lower() and '2d' in shape.lower():
|
7115
|
+
positions = dendrimer_layout_2d(Graph.AdjacencyDictionary(graph), root_index=root_index, base_radius=size/2, radius_factor=factor)
|
6829
7116
|
else:
|
6830
7117
|
if not silent:
|
6831
7118
|
print(f"{shape} is not implemented yet. Please choose from ['circle 2D', 'grid 2D', 'line 2D', 'radial 2D', 'spring 2D', 'tree 2D', 'grid 3D', 'sphere 3D', 'tree 3D']. Returning None.")
|
6832
7119
|
return None
|
6833
|
-
positions = positions.tolist()
|
6834
7120
|
if len(positions[0]) == 3:
|
6835
7121
|
positions = [[p[0], p[1], p[2]] for p in positions]
|
6836
7122
|
else:
|
@@ -7361,6 +7647,113 @@ class Graph:
|
|
7361
7647
|
json_string = json.dumps(json_data, indent=indent, sort_keys=sortKeys)
|
7362
7648
|
return json_string
|
7363
7649
|
|
7650
|
+
@staticmethod
|
7651
|
+
def Leaves(graph, edgeKey: str = None, tolerance: float = 0.0001, silent: bool = False):
|
7652
|
+
"""
|
7653
|
+
Returns a list of all vertices that have a degree of 1, also called leaf nodes.
|
7654
|
+
|
7655
|
+
Parameters
|
7656
|
+
----------
|
7657
|
+
graph : topologic_core.Graph
|
7658
|
+
The input graph.
|
7659
|
+
edgeKey : str , optional
|
7660
|
+
If specified, the value in the connected edges' dictionary specified by the edgeKey string will be aggregated to calculate
|
7661
|
+
the vertex degree. If a numeric value cannot be retrieved from an edge, a value of 1 is used instead. This is used in weighted graphs.
|
7662
|
+
tolerance : float , optional
|
7663
|
+
The desired tolerance. The default is 0.0001.
|
7664
|
+
silent : bool , optional
|
7665
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
7666
|
+
|
7667
|
+
Returns
|
7668
|
+
-------
|
7669
|
+
list
|
7670
|
+
The list of leaf nodes
|
7671
|
+
|
7672
|
+
"""
|
7673
|
+
from topologicpy.Topology import Topology
|
7674
|
+
|
7675
|
+
if not Topology.IsInstance(graph, "graph"):
|
7676
|
+
if not silent:
|
7677
|
+
print("Graph.Leaves - Error: The input graph parameter is not a valid graph. Returning None.")
|
7678
|
+
return None
|
7679
|
+
return [v for v in Graph.Vertices(graph) if Graph.VertexDegree(graph, v, edgeKey=edgeKey, tolerance=tolerance, silent=silent) == 1]
|
7680
|
+
|
7681
|
+
@staticmethod
|
7682
|
+
def LineGraph(graph, transferVertexDictionaries=False, transferEdgeDictionaries=False, tolerance=0.0001, silent=False):
|
7683
|
+
"""
|
7684
|
+
Create a line graph based on the input graph. See https://en.wikipedia.org/wiki/Line_graph.
|
7685
|
+
|
7686
|
+
Parameters
|
7687
|
+
----------
|
7688
|
+
graph : topologic_core.Graph
|
7689
|
+
The input graph.
|
7690
|
+
transferVertexDictionaries : bool, optional
|
7691
|
+
If set to True, the dictionaries of the vertices of the input graph are transferred to the edges of the line graph.
|
7692
|
+
transferEdgeDictionaries : bool, optional
|
7693
|
+
If set to True, the dictionaries of the edges of the input graph are transferred to the vertices of the line graph.
|
7694
|
+
tolerance : float, optional
|
7695
|
+
The desired tolerance. The default is 0.0001.
|
7696
|
+
silent : bool , optional
|
7697
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
7698
|
+
|
7699
|
+
Returns
|
7700
|
+
-------
|
7701
|
+
topologic_core.Graph
|
7702
|
+
The created line graph.
|
7703
|
+
|
7704
|
+
"""
|
7705
|
+
from topologicpy.Edge import Edge
|
7706
|
+
from topologicpy.Topology import Topology
|
7707
|
+
|
7708
|
+
if not Topology.IsInstance(graph, "graph"):
|
7709
|
+
if not silent:
|
7710
|
+
print("Graph.LineGraph - Error: The input graph parameter is not a valid graph. Returning None.")
|
7711
|
+
return None
|
7712
|
+
|
7713
|
+
graph_vertices = Graph.Vertices(graph)
|
7714
|
+
graph_edges = Graph.Edges(graph)
|
7715
|
+
|
7716
|
+
# Create line graph vertices (centroids of original graph edges)
|
7717
|
+
if transferEdgeDictionaries == True:
|
7718
|
+
lg_vertices = [
|
7719
|
+
Topology.SetDictionary(Topology.Centroid(edge), Topology.Dictionary(edge), silent=silent)
|
7720
|
+
for edge in graph_edges
|
7721
|
+
]
|
7722
|
+
else:
|
7723
|
+
lg_vertices = [Topology.Centroid(edge) for edge in graph_edges]
|
7724
|
+
|
7725
|
+
lg_edges = []
|
7726
|
+
if transferVertexDictionaries == True:
|
7727
|
+
for v in graph_vertices:
|
7728
|
+
edges = Graph.Edges(graph, vertices=[v])
|
7729
|
+
if len(edges) > 1:
|
7730
|
+
d = Topology.Dictionary(v) # Only need to call Dictionary once
|
7731
|
+
visited = set() # Use a set to track visited pairs of edges
|
7732
|
+
centroids = [Topology.Centroid(e) for e in edges] # Precompute centroids once
|
7733
|
+
for i in range(len(edges)):
|
7734
|
+
for j in range(i + 1, len(edges)): # Only loop over pairs (i, j) where i < j
|
7735
|
+
if (i, j) not in visited:
|
7736
|
+
lg_edge = Edge.ByVertices([centroids[i], centroids[j]], tolerance=tolerance, silent=silent)
|
7737
|
+
lg_edge = Topology.SetDictionary(lg_edge, d, silent=silent)
|
7738
|
+
lg_edges.append(lg_edge)
|
7739
|
+
visited.add((i, j))
|
7740
|
+
visited.add((j, i)) # Ensure both directions are marked as visited
|
7741
|
+
else:
|
7742
|
+
for v in graph_vertices:
|
7743
|
+
edges = Graph.Edges(graph, vertices=[v])
|
7744
|
+
if len(edges) > 1:
|
7745
|
+
visited = set() # Use a set to track visited pairs of edges
|
7746
|
+
centroids = [Topology.Centroid(e) for e in edges] # Precompute centroids once
|
7747
|
+
for i in range(len(edges)):
|
7748
|
+
for j in range(i + 1, len(edges)): # Only loop over pairs (i, j) where i < j
|
7749
|
+
if (i, j) not in visited:
|
7750
|
+
lg_edge = Edge.ByVertices([centroids[i], centroids[j]], tolerance=tolerance, silent=silent)
|
7751
|
+
lg_edges.append(lg_edge)
|
7752
|
+
visited.add((i, j))
|
7753
|
+
visited.add((j, i)) # Ensure both directions are marked as visited
|
7754
|
+
|
7755
|
+
return Graph.ByVerticesEdges(lg_vertices, lg_edges)
|
7756
|
+
|
7364
7757
|
@staticmethod
|
7365
7758
|
def LocalClusteringCoefficient(graph, vertices: list = None, key: str = "lcc", mantissa: int = 6, tolerance: float = 0.0001):
|
7366
7759
|
"""
|
@@ -8020,18 +8413,26 @@ class Graph:
|
|
8020
8413
|
return nearestVertex
|
8021
8414
|
|
8022
8415
|
@staticmethod
|
8023
|
-
def NetworkXGraph(graph, mantissa: int = 6, tolerance: float = 0.0001):
|
8416
|
+
def NetworkXGraph(graph, xKey='x', yKey='y', zKey='z', mantissa: int = 6, tolerance: float = 0.0001, silent: bool = False):
|
8024
8417
|
"""
|
8025
|
-
|
8418
|
+
Converts the input graph into a NetworkX Graph. See http://networkx.org
|
8026
8419
|
|
8027
8420
|
Parameters
|
8028
8421
|
----------
|
8029
8422
|
graph : topologic_core.Graph
|
8030
8423
|
The input graph.
|
8424
|
+
xKey : str , optional
|
8425
|
+
The dictionary key under which to save the X-Coordinate of the vertex. The default is 'x'.
|
8426
|
+
yKey : str , optional
|
8427
|
+
The dictionary key under which to save the Y-Coordinate of the vertex. The default is 'y'.
|
8428
|
+
zKey : str , optional
|
8429
|
+
The dictionary key under which to save the Z-Coordinate of the vertex. The default is 'z'.
|
8031
8430
|
mantissa : int , optional
|
8032
8431
|
The desired length of the mantissa. The default is 6.
|
8033
8432
|
tolerance : float , optional
|
8034
8433
|
The desired tolerance. The default is 0.0001.
|
8434
|
+
silent : bool , optional
|
8435
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
8035
8436
|
|
8036
8437
|
Returns
|
8037
8438
|
-------
|
@@ -8059,7 +8460,8 @@ class Graph:
|
|
8059
8460
|
return None
|
8060
8461
|
|
8061
8462
|
if not Topology.IsInstance(graph, "Graph"):
|
8062
|
-
|
8463
|
+
if not silent:
|
8464
|
+
print("Graph.NetworkXGraph - Error: The input graph is not a valid graph. Returning None.")
|
8063
8465
|
return None
|
8064
8466
|
|
8065
8467
|
nxGraph = nx.Graph()
|
@@ -8076,8 +8478,7 @@ class Graph:
|
|
8076
8478
|
values = Dictionary.Values(d)
|
8077
8479
|
if not values:
|
8078
8480
|
values = []
|
8079
|
-
keys += [
|
8080
|
-
import random
|
8481
|
+
keys += [xKey,yKey,zKey]
|
8081
8482
|
values += [Vertex.X(v, mantissa=mantissa), Vertex.Y(v, mantissa=mantissa), Vertex.Z(v, mantissa=mantissa)]
|
8082
8483
|
d = Dictionary.ByKeysValues(keys,values)
|
8083
8484
|
pythonD = Dictionary.PythonDictionary(d)
|
@@ -9160,7 +9561,7 @@ class Graph:
|
|
9160
9561
|
return Graph.ByVerticesEdges(dictionary['vertices'], dictionary['edges'])
|
9161
9562
|
|
9162
9563
|
@staticmethod
|
9163
|
-
def VertexDegree(graph, vertex, edgeKey: str = None, tolerance: float = 0.0001):
|
9564
|
+
def VertexDegree(graph, vertex, edgeKey: str = None, tolerance: float = 0.0001, silent: bool = False):
|
9164
9565
|
"""
|
9165
9566
|
Returns the degree of the input vertex. See https://en.wikipedia.org/wiki/Degree_(graph_theory).
|
9166
9567
|
|
@@ -9175,6 +9576,8 @@ class Graph:
|
|
9175
9576
|
the vertex degree. If a numeric value cannot be retrieved from an edge, a value of 1 is used instead. This is used in weighted graphs.
|
9176
9577
|
tolerance : float , optional
|
9177
9578
|
The desired tolerance. The default is 0.0001.
|
9579
|
+
silent : bool , optional
|
9580
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
9178
9581
|
|
9179
9582
|
Returns
|
9180
9583
|
-------
|
@@ -9187,10 +9590,12 @@ class Graph:
|
|
9187
9590
|
import numbers
|
9188
9591
|
|
9189
9592
|
if not Topology.IsInstance(graph, "Graph"):
|
9190
|
-
|
9593
|
+
if not silent:
|
9594
|
+
print("Graph.VertexDegree - Error: The input graph is not a valid graph. Returning None.")
|
9191
9595
|
return None
|
9192
9596
|
if not Topology.IsInstance(vertex, "Vertex"):
|
9193
|
-
|
9597
|
+
if not silent:
|
9598
|
+
print("Graph.VertexDegree - Error: The input vertex is not a valid vertex. Returning None.")
|
9194
9599
|
return None
|
9195
9600
|
if not isinstance(edgeKey, str):
|
9196
9601
|
edgeKey = ""
|