topologicpy 0.7.97__py3-none-any.whl → 0.7.99__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/Graph.py +792 -85
- topologicpy/Matrix.py +63 -0
- topologicpy/Vertex.py +83 -0
- topologicpy/version.py +1 -1
- {topologicpy-0.7.97.dist-info → topologicpy-0.7.99.dist-info}/METADATA +1 -1
- {topologicpy-0.7.97.dist-info → topologicpy-0.7.99.dist-info}/RECORD +9 -9
- {topologicpy-0.7.97.dist-info → topologicpy-0.7.99.dist-info}/LICENSE +0 -0
- {topologicpy-0.7.97.dist-info → topologicpy-0.7.99.dist-info}/WHEEL +0 -0
- {topologicpy-0.7.97.dist-info → topologicpy-0.7.99.dist-info}/top_level.txt +0 -0
topologicpy/Graph.py
CHANGED
@@ -1240,9 +1240,9 @@ class Graph:
|
|
1240
1240
|
return bot_graph.serialize(format=format)
|
1241
1241
|
|
1242
1242
|
@staticmethod
|
1243
|
-
def BetweennessCentrality(graph, key: str = "betweenness_centrality", mantissa: int = 6, tolerance: float = 0.001, silent: bool = False):
|
1243
|
+
def BetweennessCentrality(graph, key: str = "betweenness_centrality", method="vertex", mantissa: int = 6, tolerance: float = 0.001, silent: bool = False):
|
1244
1244
|
"""
|
1245
|
-
Returns the betweenness centrality
|
1245
|
+
Returns the betweenness centrality of the input graph. The order of the returned list is the same as the order of vertices/edges. See https://en.wikipedia.org/wiki/Betweenness_centrality.
|
1246
1246
|
|
1247
1247
|
Parameters
|
1248
1248
|
----------
|
@@ -1250,6 +1250,8 @@ class Graph:
|
|
1250
1250
|
The input graph.
|
1251
1251
|
key : str , optional
|
1252
1252
|
The dictionary key under which to store the betweeness centrality score. The default is "betweenness_centrality".
|
1253
|
+
method : str , optional
|
1254
|
+
The method of computing the betweenness centrality. The options are "vertex" or "edge". The default is "vertex".
|
1253
1255
|
mantissa : int , optional
|
1254
1256
|
The desired length of the mantissa. The default is 6.
|
1255
1257
|
tolerance : float , optional
|
@@ -1276,7 +1278,7 @@ class Graph:
|
|
1276
1278
|
shortest_paths[path[-1]].append(path)
|
1277
1279
|
return shortest_paths
|
1278
1280
|
|
1279
|
-
def
|
1281
|
+
def calculate_vertex_betweenness():
|
1280
1282
|
betweenness = {v: 0.0 for v in py_graph}
|
1281
1283
|
for s in py_graph:
|
1282
1284
|
shortest_paths = shortest_paths_count(s)
|
@@ -1291,6 +1293,56 @@ class Graph:
|
|
1291
1293
|
betweenness[v] += dependency[v]
|
1292
1294
|
return betweenness
|
1293
1295
|
|
1296
|
+
def calculate_edge_betweenness(graph_adj_matrix):
|
1297
|
+
n = len(graph_adj_matrix)
|
1298
|
+
edge_betweenness_scores = {}
|
1299
|
+
|
1300
|
+
# Iterate over all node pairs as source and target nodes
|
1301
|
+
for source in range(n):
|
1302
|
+
# Initialize the 'distance' and 'predecessors' for each node
|
1303
|
+
distance = [-1] * n
|
1304
|
+
predecessors = [[] for _ in range(n)]
|
1305
|
+
distance[source] = 0
|
1306
|
+
stack = []
|
1307
|
+
queue = [source]
|
1308
|
+
|
1309
|
+
# Breadth-first search to find shortest paths
|
1310
|
+
while queue:
|
1311
|
+
current_node = queue.pop(0)
|
1312
|
+
stack.append(current_node)
|
1313
|
+
for neighbor in range(n):
|
1314
|
+
if graph_adj_matrix[current_node][neighbor] == 1:
|
1315
|
+
if distance[neighbor] == -1: # First time visiting neighbor
|
1316
|
+
distance[neighbor] = distance[current_node] + 1
|
1317
|
+
queue.append(neighbor)
|
1318
|
+
if distance[neighbor] == distance[current_node] + 1: # Shortest path
|
1319
|
+
predecessors[neighbor].append(current_node)
|
1320
|
+
|
1321
|
+
# Initialize the dependency values for each node
|
1322
|
+
dependency = [0] * n
|
1323
|
+
|
1324
|
+
# Process the nodes in reverse order of discovery
|
1325
|
+
while stack:
|
1326
|
+
current_node = stack.pop()
|
1327
|
+
for pred in predecessors[current_node]:
|
1328
|
+
dependency[pred] += (1 + dependency[current_node]) / len(predecessors[current_node])
|
1329
|
+
|
1330
|
+
# Update edge betweenness scores
|
1331
|
+
if pred < current_node:
|
1332
|
+
edge = (pred, current_node)
|
1333
|
+
else:
|
1334
|
+
edge = (current_node, pred)
|
1335
|
+
|
1336
|
+
if edge not in edge_betweenness_scores:
|
1337
|
+
edge_betweenness_scores[edge] = 0
|
1338
|
+
edge_betweenness_scores[edge] += dependency[current_node]
|
1339
|
+
|
1340
|
+
# Normalize edge betweenness scores by dividing by 2 (since each edge is counted twice)
|
1341
|
+
for edge in edge_betweenness_scores:
|
1342
|
+
edge_betweenness_scores[edge] /= 2
|
1343
|
+
|
1344
|
+
return edge_betweenness_scores
|
1345
|
+
|
1294
1346
|
from topologicpy.Topology import Topology
|
1295
1347
|
from topologicpy.Dictionary import Dictionary
|
1296
1348
|
|
@@ -1299,39 +1351,145 @@ class Graph:
|
|
1299
1351
|
print("Graph.BetweenessCentrality - Error: The input graph is not a valid graph. Returning None.")
|
1300
1352
|
return None
|
1301
1353
|
|
1302
|
-
|
1354
|
+
if "v" in method.lower():
|
1355
|
+
vertices = Graph.Vertices(graph)
|
1303
1356
|
|
1304
|
-
|
1305
|
-
|
1306
|
-
|
1307
|
-
|
1308
|
-
|
1309
|
-
|
1310
|
-
|
1311
|
-
|
1312
|
-
|
1313
|
-
|
1314
|
-
|
1315
|
-
|
1316
|
-
|
1317
|
-
|
1357
|
+
if len(vertices) < 1:
|
1358
|
+
if not silent:
|
1359
|
+
print("Graph.BetweenessCentrality - Error: The input graph does not contain valid vertices. Returning None.")
|
1360
|
+
return None
|
1361
|
+
if len(vertices) == 1:
|
1362
|
+
d = Topology.Dictionary(vertices[0])
|
1363
|
+
d = Dictionary.SetValueAtKey(d, key, 1.0)
|
1364
|
+
vertices[0] = Topology.SetDictionary(vertices[0], d)
|
1365
|
+
return [1.0]
|
1366
|
+
|
1367
|
+
py_graph = Graph.AdjacencyDictionary(graph)
|
1368
|
+
vertex_betweenness = calculate_vertex_betweenness()
|
1369
|
+
for v in vertex_betweenness:
|
1370
|
+
vertex_betweenness[v] /= 2.0 # Each shortest path is counted twice
|
1371
|
+
|
1372
|
+
min_betweenness = min(vertex_betweenness.values())
|
1373
|
+
max_betweenness = max(vertex_betweenness.values())
|
1374
|
+
if (max_betweenness - min_betweenness) > 0:
|
1375
|
+
for v in vertex_betweenness:
|
1376
|
+
vertex_betweenness[v] = (vertex_betweenness[v] - min_betweenness)/ (max_betweenness - min_betweenness) # Normalize to [0, 1]
|
1377
|
+
|
1378
|
+
|
1379
|
+
vertex_betweenness_scores = [0]*len(vertices)
|
1380
|
+
for i, score in vertex_betweenness.items():
|
1381
|
+
vertex = vertices[int(i)]
|
1382
|
+
d = Topology.Dictionary(vertex)
|
1383
|
+
d = Dictionary.SetValueAtKey(d, key, round(score, mantissa))
|
1384
|
+
vertex = Topology.SetDictionary(vertex, d)
|
1385
|
+
vertex_betweenness_scores[int(i)] = round(score, mantissa)
|
1386
|
+
|
1387
|
+
return vertex_betweenness_scores
|
1388
|
+
else:
|
1389
|
+
graph_edges = Graph.Edges(graph)
|
1390
|
+
adj_matrix = Graph.AdjacencyMatrix(graph)
|
1391
|
+
meshData = Graph.MeshData(graph)
|
1392
|
+
edges = meshData["edges"]
|
1393
|
+
if len(graph_edges) < 1:
|
1394
|
+
if not silent:
|
1395
|
+
print("Graph.BetweenessCentrality - Error: The input graph does not contain any edges. Returning None.")
|
1396
|
+
return None
|
1397
|
+
if len(graph_edges) == 1:
|
1398
|
+
d = Topology.Dictionary(graph_edges[0])
|
1399
|
+
d = Dictionary.SetValueAtKey(d, key, 1.0)
|
1400
|
+
graph_edges[0] = Topology.SetDictionary(graph_edges[0], d)
|
1401
|
+
return [1.0]
|
1402
|
+
|
1403
|
+
edge_betweenness = calculate_edge_betweenness(adj_matrix)
|
1404
|
+
keys = list(edge_betweenness.keys())
|
1405
|
+
values = list(edge_betweenness.values())
|
1406
|
+
min_value = min(values)
|
1407
|
+
max_value = max(values)
|
1408
|
+
edge_betweenness_scores = []
|
1409
|
+
for i, edge in enumerate(edges):
|
1410
|
+
u,v = edge
|
1411
|
+
if (u,v) in keys:
|
1412
|
+
score = edge_betweenness[(u,v)]
|
1413
|
+
elif (v,u) in keys:
|
1414
|
+
score = edge_betweenness[(v,u)]
|
1415
|
+
else:
|
1416
|
+
score = 0
|
1417
|
+
score = (score - min_value)/(max_value - min_value)
|
1418
|
+
edge_betweenness_scores.append(round(score, mantissa))
|
1419
|
+
d = Topology.Dictionary(graph_edges[i])
|
1420
|
+
d = Dictionary.SetValueAtKey(d, key, round(score, mantissa))
|
1421
|
+
graph_edges[i] = Topology.SetDictionary(graph_edges[i], d)
|
1422
|
+
return edge_betweenness_scores
|
1318
1423
|
|
1319
|
-
|
1320
|
-
|
1321
|
-
|
1322
|
-
|
1323
|
-
|
1324
|
-
|
1325
|
-
return_betweenness = [0]*len(vertices)
|
1326
|
-
for i, v in betweenness.items():
|
1327
|
-
vertex = vertices[int(i)]
|
1328
|
-
d = Topology.Dictionary(vertex)
|
1329
|
-
d = Dictionary.SetValueAtKey(d, key, round(v, mantissa))
|
1330
|
-
vertex = Topology.SetDictionary(vertex, d)
|
1331
|
-
return_betweenness[int(i)] = v
|
1332
|
-
|
1333
|
-
return return_betweenness
|
1424
|
+
@staticmethod
|
1425
|
+
def BetweennessPartition(graph, n=2, m=10, key="partition", tolerance=0.0001, silent=False):
|
1426
|
+
"""
|
1427
|
+
Computes a partition of the input graph based on the edge betweenness method. See https://en.wikipedia.org/wiki/Graph_partition.
|
1334
1428
|
|
1429
|
+
Parameters
|
1430
|
+
----------
|
1431
|
+
graph : topologicp.Graph
|
1432
|
+
The input topologic graph.
|
1433
|
+
n : int , optional
|
1434
|
+
The desired number of partitions when selecting the "Betweenness" method. This parameter is ignored for other methods. The default is 2.
|
1435
|
+
m : int , optional
|
1436
|
+
The desired maximum number of tries to partition the graph when selecting the "Betweenness" method. This parameter is ignored for other methods. The default is 10.
|
1437
|
+
key : str , optional
|
1438
|
+
The vertex and edge dictionary key under which to store the parition number. The default is "partition".
|
1439
|
+
Valid partition numbers start from 1. Cut edges receive a partition number of 0.
|
1440
|
+
tolerance : float , optional
|
1441
|
+
The desired tolerance. The default is 0.0001.
|
1442
|
+
silent : bool , optional
|
1443
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
1444
|
+
|
1445
|
+
Returns
|
1446
|
+
-------
|
1447
|
+
topologicpy.Graph
|
1448
|
+
The partitioned topologic graph.
|
1449
|
+
|
1450
|
+
"""
|
1451
|
+
from topologicpy.Topology import Topology
|
1452
|
+
from topologicpy.Helper import Helper
|
1453
|
+
from topologicpy.Dictionary import Dictionary
|
1454
|
+
|
1455
|
+
edge_scores = Graph.BetweennessCentrality(graph, method="edge")
|
1456
|
+
graph_edges = Graph.Edges(graph)
|
1457
|
+
|
1458
|
+
graph_edges = Helper.Sort(graph_edges, edge_scores)
|
1459
|
+
graph_edges.reverse()
|
1460
|
+
cut_edges = []
|
1461
|
+
components = 1
|
1462
|
+
tries = 0
|
1463
|
+
while components < n and tries < m:
|
1464
|
+
components = len(Graph.ConnectedComponents(graph, key=key, tolerance=tolerance, silent=silent))
|
1465
|
+
if components == n:
|
1466
|
+
if not silent:
|
1467
|
+
print("Graph.BetweennessPartition - Warning: The input graph is already partitioned into partitions that are equal in number to the desired number of partitions.")
|
1468
|
+
return graph
|
1469
|
+
elif components > n:
|
1470
|
+
if not silent:
|
1471
|
+
print("Graph.BetweennessPartition - Warning: The input graph is already partitioned into partitions that are greater in number than the desired number of partitions.")
|
1472
|
+
return graph
|
1473
|
+
elif len(graph_edges) < 1:
|
1474
|
+
components = n
|
1475
|
+
else:
|
1476
|
+
edge = graph_edges[0]
|
1477
|
+
d = Topology.Dictionary(edge)
|
1478
|
+
d = Dictionary.SetValueAtKey(d, key, 0) # 0 indicates a cut edge
|
1479
|
+
edge = Topology.SetDictionary(edge, d)
|
1480
|
+
cut_edges.append(edge)
|
1481
|
+
graph = Graph.RemoveEdge(graph, edge, tolerance=tolerance)
|
1482
|
+
graph_edges = graph_edges[1:]
|
1483
|
+
components = len(Graph.ConnectedComponents(graph, key=key, tolerance=tolerance, silent=silent))
|
1484
|
+
tries += 1
|
1485
|
+
if tries == m:
|
1486
|
+
if not silent:
|
1487
|
+
print("Graph.Partition - Warning: Reached the maximum number of tries.")
|
1488
|
+
return_vertices = Graph.Vertices(graph)
|
1489
|
+
return_edges = Graph.Edges(graph) + cut_edges
|
1490
|
+
return_graph = Graph.ByVerticesEdges(return_vertices, return_edges)
|
1491
|
+
return return_graph
|
1492
|
+
|
1335
1493
|
@staticmethod
|
1336
1494
|
def Bridges(graph, key: str = "bridge", silent: bool = False):
|
1337
1495
|
"""
|
@@ -1345,59 +1503,91 @@ class Graph:
|
|
1345
1503
|
The edge dictionary key under which to store the bridge status. 0 means the edge is NOT a bridge. 1 means that the edge IS a bridge. The default is "bridge".
|
1346
1504
|
silent : bool , optional
|
1347
1505
|
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
1348
|
-
|
1506
|
+
|
1349
1507
|
Returns
|
1350
1508
|
-------
|
1351
1509
|
list
|
1352
1510
|
The list of bridge edges in the input graph.
|
1353
|
-
|
1354
1511
|
"""
|
1355
|
-
import os
|
1356
|
-
import warnings
|
1357
1512
|
from topologicpy.Topology import Topology
|
1358
1513
|
from topologicpy.Dictionary import Dictionary
|
1514
|
+
from topologicpy.Graph import Graph
|
1359
1515
|
|
1360
|
-
try:
|
1361
|
-
import igraph as ig
|
1362
|
-
except:
|
1363
|
-
print("Graph.Bridges - Installing required pyhon-igraph library.")
|
1364
|
-
try:
|
1365
|
-
os.system("pip install python-igraph")
|
1366
|
-
except:
|
1367
|
-
os.system("pip install python-igraph --user")
|
1368
|
-
try:
|
1369
|
-
import igraph as ig
|
1370
|
-
print("Graph.Bridges - python-igraph library installed correctly.")
|
1371
|
-
except:
|
1372
|
-
warnings.warn("Graph.Bridges - Error: Could not import python-igraph. Please install manually.")
|
1373
|
-
|
1374
1516
|
if not Topology.IsInstance(graph, "graph"):
|
1375
1517
|
if not silent:
|
1376
1518
|
print("Graph.Bridges - Error: The input graph parameter is not a valid topologic graph. Returning None")
|
1377
1519
|
return None
|
1378
1520
|
|
1379
|
-
|
1380
|
-
|
1381
|
-
return []
|
1382
|
-
if len(edges) == 1:
|
1521
|
+
graph_edges = Graph.Edges(graph)
|
1522
|
+
for edge in graph_edges:
|
1383
1523
|
d = Topology.Dictionary(edge)
|
1384
|
-
d = Dictionary.SetValueAtKey(d, key,
|
1524
|
+
d = Dictionary.SetValueAtKey(d, key, 0)
|
1385
1525
|
edge = Topology.SetDictionary(edge, d)
|
1386
|
-
return [edge]
|
1387
1526
|
mesh_data = Graph.MeshData(graph)
|
1388
|
-
|
1389
|
-
ig_graph = ig.Graph(edges=graph_edges)
|
1527
|
+
mesh_edges = mesh_data['edges']
|
1390
1528
|
|
1391
|
-
|
1529
|
+
# Get adjacency dictionary
|
1530
|
+
adjacency_dict = Graph.AdjacencyDictionary(graph)
|
1531
|
+
if not adjacency_dict:
|
1532
|
+
if not silent:
|
1533
|
+
print("Graph.Bridges - Error: Failed to retrieve adjacency dictionary. Returning None")
|
1534
|
+
return None
|
1535
|
+
|
1536
|
+
# Helper function to perform DFS and find bridges
|
1537
|
+
def dfs(vertex, parent, time, low, disc, visited, adjacency_dict, bridges, edge_map):
|
1538
|
+
visited[int(vertex)] = True
|
1539
|
+
disc[int(vertex)] = low[int(vertex)] = time[0]
|
1540
|
+
time[0] += 1
|
1541
|
+
for neighbor in adjacency_dict[vertex]:
|
1542
|
+
if not visited[int(neighbor)]:
|
1543
|
+
dfs(neighbor, vertex, time, low, disc, visited, adjacency_dict, bridges, edge_map)
|
1544
|
+
low[int(vertex)] = min(low[int(vertex)], low[int(neighbor)])
|
1545
|
+
|
1546
|
+
# Check if edge is a bridge
|
1547
|
+
if low[int(neighbor)] > disc[int(vertex)]:
|
1548
|
+
bridges.add((vertex, neighbor))
|
1549
|
+
elif neighbor != parent:
|
1550
|
+
low[int(vertex)] = min(low[int(vertex)], disc[int(neighbor)])
|
1551
|
+
|
1552
|
+
# Prepare adjacency list and edge mapping
|
1553
|
+
vertices = list(adjacency_dict.keys())
|
1554
|
+
num_vertices = len(vertices)
|
1555
|
+
visited = [False] * num_vertices
|
1556
|
+
disc = [-1] * num_vertices
|
1557
|
+
low = [-1] * num_vertices
|
1558
|
+
time = [0]
|
1559
|
+
bridges = set()
|
1560
|
+
|
1561
|
+
# Map edges to indices
|
1562
|
+
edge_map = {}
|
1563
|
+
index = 0
|
1564
|
+
for vertex, neighbors in adjacency_dict.items():
|
1565
|
+
for neighbor in neighbors:
|
1566
|
+
if (neighbor, vertex) not in edge_map: # Avoid duplicating edges in undirected graphs
|
1567
|
+
edge_map[(vertex, neighbor)] = index
|
1568
|
+
index += 1
|
1569
|
+
|
1570
|
+
# Run DFS from all unvisited vertices
|
1571
|
+
for i, vertex in enumerate(vertices):
|
1572
|
+
if not visited[i]:
|
1573
|
+
dfs(vertex, -1, time, low, disc, visited, adjacency_dict, bridges, edge_map)
|
1574
|
+
|
1575
|
+
# Mark bridges in the edges' dictionaries
|
1392
1576
|
bridge_edges = []
|
1393
|
-
for
|
1577
|
+
for edge in bridges:
|
1578
|
+
i, j = edge
|
1579
|
+
i = int(i)
|
1580
|
+
j = int(j)
|
1581
|
+
try:
|
1582
|
+
edge_index = mesh_edges.index([i,j])
|
1583
|
+
except:
|
1584
|
+
edge_index = mesh_edges.index([j,i])
|
1585
|
+
bridge_edges.append(graph_edges[edge_index])
|
1586
|
+
for edge in bridge_edges:
|
1394
1587
|
d = Topology.Dictionary(edge)
|
1395
|
-
|
1396
|
-
d = Dictionary.SetValueAtKey(d, key, 1)
|
1397
|
-
bridge_edges.append(edge)
|
1398
|
-
else:
|
1399
|
-
d = Dictionary.SetValueAtKey(d, key, 0)
|
1588
|
+
d = Dictionary.SetValueAtKey(d, key, 1)
|
1400
1589
|
edge = Topology.SetDictionary(edge, d)
|
1590
|
+
|
1401
1591
|
return bridge_edges
|
1402
1592
|
|
1403
1593
|
@staticmethod
|
@@ -1486,7 +1676,8 @@ class Graph:
|
|
1486
1676
|
for i in range(len(adjacencyMatrix)):
|
1487
1677
|
x, y, z = random.uniform(xMin,xMax), random.uniform(yMin,yMax), random.uniform(zMin,zMax)
|
1488
1678
|
v = Vertex.ByCoordinates(x, y, z)
|
1489
|
-
|
1679
|
+
if isinstance(dictionaries, list):
|
1680
|
+
v = Topology.SetDictionary(v, dictionaries[i])
|
1490
1681
|
vertices.append(v)
|
1491
1682
|
|
1492
1683
|
# Create the graph using vertices and edges
|
@@ -4153,6 +4344,82 @@ class Graph:
|
|
4153
4344
|
v = Topology.SetDictionary(v, d)
|
4154
4345
|
return graph
|
4155
4346
|
|
4347
|
+
@staticmethod
|
4348
|
+
def Complement(graph, tolerance=0.0001, silent=False):
|
4349
|
+
"""
|
4350
|
+
Creates the complement graph of the input graph. See https://en.wikipedia.org/wiki/Complement_graph
|
4351
|
+
|
4352
|
+
Parameters
|
4353
|
+
----------
|
4354
|
+
graph : topologicpy.Graph
|
4355
|
+
The input topologic graph.
|
4356
|
+
tolerance : float , optional
|
4357
|
+
The desired tolerance. The default is 0.0001.
|
4358
|
+
silent : bool , optional
|
4359
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
4360
|
+
|
4361
|
+
Returns
|
4362
|
+
-------
|
4363
|
+
topologicpy.Graph
|
4364
|
+
The created complement topologic graph.
|
4365
|
+
|
4366
|
+
"""
|
4367
|
+
def complement_graph(adj_dict):
|
4368
|
+
"""
|
4369
|
+
Creates the complement graph from an input adjacency dictionary.
|
4370
|
+
|
4371
|
+
Parameters:
|
4372
|
+
adj_dict (dict): The adjacency dictionary where keys are nodes and
|
4373
|
+
values are lists of connected nodes.
|
4374
|
+
|
4375
|
+
Returns:
|
4376
|
+
list of tuples: A list of edge index tuples representing the complement graph.
|
4377
|
+
"""
|
4378
|
+
# Get all nodes in the graph
|
4379
|
+
nodes = list(adj_dict.keys())
|
4380
|
+
# Initialize a set to store edges of the complement graph
|
4381
|
+
complement_edges = set()
|
4382
|
+
# Convert adjacency dictionary to a set of existing edges
|
4383
|
+
existing_edges = set()
|
4384
|
+
for node, neighbors in adj_dict.items():
|
4385
|
+
for neighbor in neighbors:
|
4386
|
+
# Add the edge as an ordered tuple to ensure no duplicates
|
4387
|
+
existing_edges.add(tuple(sorted((node, neighbor))))
|
4388
|
+
# Generate all possible edges and check if they exist in the original graph
|
4389
|
+
for i, node1 in enumerate(nodes):
|
4390
|
+
for j in range(i + 1, len(nodes)):
|
4391
|
+
node2 = nodes[j]
|
4392
|
+
edge = tuple(sorted((node1, node2)))
|
4393
|
+
# Add the edge if it's not in the original graph
|
4394
|
+
if edge not in existing_edges:
|
4395
|
+
complement_edges.add(edge)
|
4396
|
+
# Return the complement edges as a sorted list of tuples
|
4397
|
+
return sorted(complement_edges)
|
4398
|
+
|
4399
|
+
from topologicpy.Graph import Graph
|
4400
|
+
from topologicpy.Edge import Edge
|
4401
|
+
from topologicpy.Vertex import Vertex
|
4402
|
+
from topologicpy.Topology import Topology
|
4403
|
+
|
4404
|
+
if not Topology.IsInstance(graph, "graph"):
|
4405
|
+
if not silent:
|
4406
|
+
print("Graph.Complement - Error: The input graph parameter is not a valid topologic graph. Returning None.")
|
4407
|
+
return None
|
4408
|
+
adj_dict = Graph.AdjacencyDictionary(graph)
|
4409
|
+
py_edges = complement_graph(adj_dict)
|
4410
|
+
vertices = Graph.Vertices(graph)
|
4411
|
+
adjusted_vertices = Vertex.Separate(vertices, minDistance=tolerance)
|
4412
|
+
edges = []
|
4413
|
+
for py_edge in py_edges:
|
4414
|
+
start, end = py_edge
|
4415
|
+
sv = adjusted_vertices[int(start)]
|
4416
|
+
ev = adjusted_vertices[int(end)]
|
4417
|
+
edge = Edge.ByVertices(sv, ev, tolerance=tolerance, silent=silent)
|
4418
|
+
if Topology.IsInstance(edge, "edge"):
|
4419
|
+
edges.append(edge)
|
4420
|
+
return_graph = Graph.ByVerticesEdges(adjusted_vertices, edges)
|
4421
|
+
return return_graph
|
4422
|
+
|
4156
4423
|
@staticmethod
|
4157
4424
|
def Complete(graph, silent: bool = False):
|
4158
4425
|
"""
|
@@ -4196,7 +4463,7 @@ class Graph:
|
|
4196
4463
|
return Graph.ByVerticesEdges(vertices, edges)
|
4197
4464
|
|
4198
4465
|
@staticmethod
|
4199
|
-
def ConnectedComponents(graph, tolerance: float = 0.0001, silent: bool = False):
|
4466
|
+
def ConnectedComponents(graph, key: str = "component", tolerance: float = 0.0001, silent: bool = False):
|
4200
4467
|
"""
|
4201
4468
|
Returns the connected components (islands) of the input graph.
|
4202
4469
|
|
@@ -4204,6 +4471,8 @@ class Graph:
|
|
4204
4471
|
----------
|
4205
4472
|
graph : topologic_core.Graph
|
4206
4473
|
The input graph.
|
4474
|
+
key : str , optional
|
4475
|
+
The vertex and edge dictionary key under which to store the component number. The default is "component".
|
4207
4476
|
tolerance : float , optional
|
4208
4477
|
The desired tolerance. The default is 0.0001.
|
4209
4478
|
silent : bool , optional
|
@@ -4249,23 +4518,36 @@ class Graph:
|
|
4249
4518
|
labelKey = "__label__"
|
4250
4519
|
lengths = [] #List of lengths to sort the list of components by number of their vertices
|
4251
4520
|
vertices = Graph.Vertices(graph)
|
4521
|
+
vertex_map = {}
|
4522
|
+
for i, v in enumerate(vertices):
|
4523
|
+
d = Topology.Dictionary(v)
|
4524
|
+
d = Dictionary.SetValueAtKey(d, labelKey, i)
|
4525
|
+
v = Topology.SetDictionary(v, d)
|
4526
|
+
vertex_map[i] = v
|
4252
4527
|
g_dict = Graph.AdjacencyDictionary(graph, vertexLabelKey=labelKey)
|
4253
4528
|
components = find_connected_components(g_dict)
|
4254
4529
|
return_components = []
|
4255
|
-
for component in components:
|
4530
|
+
for i, component in enumerate(components):
|
4256
4531
|
i_verts = []
|
4257
|
-
for v in component:
|
4258
|
-
vert =
|
4532
|
+
for v in component:
|
4533
|
+
vert = vertex_map[v]
|
4259
4534
|
d = Topology.Dictionary(vert)
|
4260
4535
|
d = Dictionary.RemoveKey(d, labelKey)
|
4536
|
+
d = Dictionary.SetValueAtKey(d, key, i+1)
|
4261
4537
|
vert = Topology.SetDictionary(vert, d)
|
4262
4538
|
i_verts.append(vert)
|
4263
|
-
|
4264
|
-
|
4265
|
-
|
4266
|
-
|
4267
|
-
|
4268
|
-
|
4539
|
+
if len(i_verts) > 0:
|
4540
|
+
i_edges = Graph.Edges(graph, i_verts)
|
4541
|
+
for i_edge in i_edges:
|
4542
|
+
d = Topology.Dictionary(i_edge)
|
4543
|
+
d = Dictionary.SetValueAtKey(d, key, i+1)
|
4544
|
+
i_edge = Topology.SetDictionary(i_edge, d)
|
4545
|
+
lengths.append(len(i_verts))
|
4546
|
+
g_component = Graph.ByVerticesEdges(i_verts, i_edges)
|
4547
|
+
return_components.append(g_component)
|
4548
|
+
if len(return_components) > 0:
|
4549
|
+
return_components = Helper.Sort(return_components, lengths)
|
4550
|
+
return_components.reverse()
|
4269
4551
|
return return_components
|
4270
4552
|
|
4271
4553
|
@staticmethod
|
@@ -4460,17 +4742,16 @@ class Graph:
|
|
4460
4742
|
return centralities
|
4461
4743
|
|
4462
4744
|
@staticmethod
|
4463
|
-
def Community(graph, key: str = "
|
4745
|
+
def Community(graph, key: str = "partition", mantissa: int = 6, tolerance: float = 0.0001, silent: bool = False):
|
4464
4746
|
"""
|
4465
4747
|
Computes the best community partition of the input graph based on the Louvain method. See https://en.wikipedia.org/wiki/Louvain_method.
|
4466
|
-
This method depends on NetworkX and the python-louvain libraries
|
4467
4748
|
|
4468
4749
|
Parameters
|
4469
4750
|
----------
|
4470
4751
|
graph : topologicp.Graph
|
4471
4752
|
The input topologic graph.
|
4472
4753
|
key : str , optional
|
4473
|
-
The dictionary key under which to store the
|
4754
|
+
The dictionary key under which to store the partition number. The default is "partition".
|
4474
4755
|
mantissa : int , optional
|
4475
4756
|
The desired length of the mantissa. The default is 6.
|
4476
4757
|
tolerance : float , optional
|
@@ -4480,9 +4761,38 @@ class Graph:
|
|
4480
4761
|
Returns
|
4481
4762
|
-------
|
4482
4763
|
topologicpy.Graph
|
4483
|
-
The
|
4764
|
+
The partitioned topologic graph.
|
4765
|
+
|
4766
|
+
"""
|
4767
|
+
if not silent:
|
4768
|
+
print("Graph.Community - Warning: This method is deprectated. Please use Graph.CommunityPartition instead.")
|
4769
|
+
return Graph.CommunityPartition(graph=graph, key=key, mantissa=mantissa, tolerance=tolerance, silent=silent)
|
4770
|
+
|
4771
|
+
@staticmethod
|
4772
|
+
def CommunityPartition(graph, key: str = "partition", mantissa: int = 6, tolerance: float = 0.0001, silent: bool = False):
|
4773
|
+
"""
|
4774
|
+
Computes the best community partition of the input graph based on the Louvain method. See https://en.wikipedia.org/wiki/Louvain_method.
|
4775
|
+
|
4776
|
+
Parameters
|
4777
|
+
----------
|
4778
|
+
graph : topologicp.Graph
|
4779
|
+
The input topologic graph.
|
4780
|
+
key : str , optional
|
4781
|
+
The dictionary key under which to store the partition number. The default is "partition".
|
4782
|
+
mantissa : int , optional
|
4783
|
+
The desired length of the mantissa. The default is 6.
|
4784
|
+
tolerance : float , optional
|
4785
|
+
The desired tolerance. The default is 0.0001.
|
4786
|
+
silent : bool , optional
|
4787
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
4788
|
+
Returns
|
4789
|
+
-------
|
4790
|
+
topologicpy.Graph
|
4791
|
+
The partitioned topologic graph.
|
4484
4792
|
|
4485
4793
|
"""
|
4794
|
+
from topologicpy.Vertex import Vertex
|
4795
|
+
from topologicpy.Edge import Edge
|
4486
4796
|
from topologicpy.Topology import Topology
|
4487
4797
|
from topologicpy.Dictionary import Dictionary
|
4488
4798
|
import os
|
@@ -4515,13 +4825,39 @@ class Graph:
|
|
4515
4825
|
communities = ig_graph.community_multilevel()
|
4516
4826
|
|
4517
4827
|
# Get the list of communities sorted same as vertices
|
4518
|
-
|
4828
|
+
partition_list = communities.membership
|
4519
4829
|
vertices = Graph.Vertices(graph)
|
4520
4830
|
for i, v in enumerate(vertices):
|
4521
4831
|
d = Topology.Dictionary(v)
|
4522
|
-
d = Dictionary.SetValueAtKey(d, key,
|
4832
|
+
d = Dictionary.SetValueAtKey(d, key, partition_list[i]+1)
|
4523
4833
|
v = Topology.SetDictionary(v, d)
|
4524
|
-
|
4834
|
+
edges = Graph.Edges(graph)
|
4835
|
+
if not edges == None:
|
4836
|
+
for edge in edges:
|
4837
|
+
sv = Edge.StartVertex(edge)
|
4838
|
+
ev = Edge.EndVertex(edge)
|
4839
|
+
status_1 = False
|
4840
|
+
status_2 = False
|
4841
|
+
partition_1 = 0
|
4842
|
+
partition_2 = 0
|
4843
|
+
for i, v in enumerate(vertices):
|
4844
|
+
if Vertex.IsCoincident(sv, v, tolerance=tolerance):
|
4845
|
+
status_1 = True
|
4846
|
+
partition_1 = Dictionary.ValueAtKey(Topology.Dictionary(v), key)
|
4847
|
+
break
|
4848
|
+
for i, v in enumerate(vertices):
|
4849
|
+
if Vertex.IsCoincident(ev, v, tolerance=tolerance):
|
4850
|
+
status_2 = True
|
4851
|
+
partition_2 = Dictionary.ValueAtKey(Topology.Dictionary(v), key)
|
4852
|
+
break
|
4853
|
+
partition = 0
|
4854
|
+
if status_1 and status_2:
|
4855
|
+
if partition_1 == partition_2:
|
4856
|
+
partition = partition_1
|
4857
|
+
d = Topology.Dictionary(edge)
|
4858
|
+
d = Dictionary.SetValueAtKey(d, key, partition)
|
4859
|
+
edge = Topology.SetDictionary(edge, d)
|
4860
|
+
return graph
|
4525
4861
|
|
4526
4862
|
@staticmethod
|
4527
4863
|
def Connect(graph, verticesA, verticesB, tolerance=0.0001):
|
@@ -4793,6 +5129,28 @@ class Graph:
|
|
4793
5129
|
scores.append(degree)
|
4794
5130
|
return scores
|
4795
5131
|
|
5132
|
+
@staticmethod
|
5133
|
+
def DegreeMatrix(graph):
|
5134
|
+
"""
|
5135
|
+
Returns the degree matrix of the input graph. See https://en.wikipedia.org/wiki/Degree_matrix.
|
5136
|
+
|
5137
|
+
Parameters
|
5138
|
+
----------
|
5139
|
+
graph : topologic_core.Graph
|
5140
|
+
The input graph.
|
5141
|
+
|
5142
|
+
Returns
|
5143
|
+
-------
|
5144
|
+
list
|
5145
|
+
The degree matrix of the input graph.
|
5146
|
+
|
5147
|
+
"""
|
5148
|
+
import numpy as np
|
5149
|
+
adj_matrix = Graph.AdjacencyMatrix(g)
|
5150
|
+
np_adj_matrix = np.array(adj_matrix)
|
5151
|
+
degree_matrix = np.diag(np_adj_matrix.sum(axis=1))
|
5152
|
+
return degree_matrix.tolist()
|
5153
|
+
|
4796
5154
|
@staticmethod
|
4797
5155
|
def DegreeSequence(graph):
|
4798
5156
|
"""
|
@@ -6161,6 +6519,111 @@ class Graph:
|
|
6161
6519
|
return False
|
6162
6520
|
return False
|
6163
6521
|
|
6522
|
+
@staticmethod
|
6523
|
+
def FiedlerVector(graph, mantissa = 6, silent: bool = False):
|
6524
|
+
"""
|
6525
|
+
Computes the Fiedler vector of a graph. See https://en.wikipedia.org/wiki/Algebraic_connectivity.
|
6526
|
+
|
6527
|
+
Parameters
|
6528
|
+
----------
|
6529
|
+
graph : topologic_core.Graph
|
6530
|
+
The input graph
|
6531
|
+
mantissa : int , optional
|
6532
|
+
The desired length of the mantissa. The default is 6.
|
6533
|
+
silent : bool , optional
|
6534
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
6535
|
+
|
6536
|
+
Returns
|
6537
|
+
-------
|
6538
|
+
list
|
6539
|
+
The Fiedler vector (eigenvector corresponding to the second smallest eigenvalue).
|
6540
|
+
"""
|
6541
|
+
from topologicpy.Topology import Topology
|
6542
|
+
from topologicpy.Matrix import Matrix
|
6543
|
+
import numpy as np
|
6544
|
+
|
6545
|
+
if not Topology.IsInstance(graph, "graph"):
|
6546
|
+
if not silent:
|
6547
|
+
print("Graph.FiedlerVector - Error: The input graph parameter is not a valid graph. Returning None.")
|
6548
|
+
|
6549
|
+
laplacian = Graph.Laplacian(graph)
|
6550
|
+
eigenvalues, eigenvectors = Matrix.EigenvaluesAndVectors(laplacian, mantissa=mantissa)
|
6551
|
+
return eigenvectors[1]
|
6552
|
+
|
6553
|
+
@staticmethod
|
6554
|
+
def FiedlerVectorPartition(graph, key="partition", mantissa: int = 6, tolerance: float = 0.0001, silent: bool = False):
|
6555
|
+
"""
|
6556
|
+
Partitions the input graph based on FiedlerVector method. See https://en.wikipedia.org/wiki/Graph_partition.
|
6557
|
+
|
6558
|
+
Parameters
|
6559
|
+
----------
|
6560
|
+
graph : topologicp.Graph
|
6561
|
+
The input topologic graph.
|
6562
|
+
key : str , optional
|
6563
|
+
The vertex and edge dictionary key under which to store the parition number. The default is "partition".
|
6564
|
+
Valid partition numbers start from 1. Cut edges receive a partition number of 0.
|
6565
|
+
mantissa : int , optional
|
6566
|
+
The desired length of the mantissa. The default is 6.
|
6567
|
+
tolerance : float , optional
|
6568
|
+
The desired tolerance. The default is 0.0001.
|
6569
|
+
silent : bool , optional
|
6570
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
6571
|
+
|
6572
|
+
Returns
|
6573
|
+
-------
|
6574
|
+
topologicpy.Graph
|
6575
|
+
The partitioned topologic graph.
|
6576
|
+
|
6577
|
+
"""
|
6578
|
+
from topologicpy.Vertex import Vertex
|
6579
|
+
from topologicpy.Edge import Edge
|
6580
|
+
from topologicpy.Topology import Topology
|
6581
|
+
from topologicpy.Dictionary import Dictionary
|
6582
|
+
|
6583
|
+
if not Topology.IsInstance(graph, "graph"):
|
6584
|
+
if not silent:
|
6585
|
+
print("Graph.FiedlerVectorPartition - Error: The input graph parameter is not a valid topologic graph. Returning None.")
|
6586
|
+
return None
|
6587
|
+
|
6588
|
+
fiedler_vector = Graph.FiedlerVector(graph, mantissa=mantissa, silent=silent)
|
6589
|
+
|
6590
|
+
vertices = Graph.Vertices(graph)
|
6591
|
+
for i, f_v in enumerate(fiedler_vector):
|
6592
|
+
if f_v >=0:
|
6593
|
+
partition = 1
|
6594
|
+
else:
|
6595
|
+
partition = 2
|
6596
|
+
d = Topology.Dictionary(vertices[i])
|
6597
|
+
d = Dictionary.SetValueAtKey(d, key, partition)
|
6598
|
+
vertices[i] = Topology.SetDictionary(vertices[i], d)
|
6599
|
+
edges = Graph.Edges(graph)
|
6600
|
+
if not edges == None:
|
6601
|
+
for edge in edges:
|
6602
|
+
sv = Edge.StartVertex(edge)
|
6603
|
+
ev = Edge.EndVertex(edge)
|
6604
|
+
status_1 = False
|
6605
|
+
status_2 = False
|
6606
|
+
partition_1 = 0
|
6607
|
+
partition_2 = 0
|
6608
|
+
for i, v in enumerate(vertices):
|
6609
|
+
if Vertex.IsCoincident(sv, v, tolerance=tolerance):
|
6610
|
+
status_1 = True
|
6611
|
+
partition_1 = Dictionary.ValueAtKey(Topology.Dictionary(v), key)
|
6612
|
+
break
|
6613
|
+
for i, v in enumerate(vertices):
|
6614
|
+
if Vertex.IsCoincident(ev, v, tolerance=tolerance):
|
6615
|
+
status_2 = True
|
6616
|
+
partition_2 = Dictionary.ValueAtKey(Topology.Dictionary(v), key)
|
6617
|
+
break
|
6618
|
+
partition = 0
|
6619
|
+
if status_1 and status_2:
|
6620
|
+
if partition_1 == partition_2:
|
6621
|
+
partition = partition_1
|
6622
|
+
d = Topology.Dictionary(edge)
|
6623
|
+
d = Dictionary.SetValueAtKey(d, key, partition)
|
6624
|
+
edge = Topology.SetDictionary(edge, d)
|
6625
|
+
return graph
|
6626
|
+
|
6164
6627
|
@staticmethod
|
6165
6628
|
def IsIsomorphic(graphA, graphB, maxIterations=10, silent=False):
|
6166
6629
|
"""
|
@@ -7511,6 +7974,59 @@ class Graph:
|
|
7511
7974
|
return None
|
7512
7975
|
return graph.IsComplete()
|
7513
7976
|
|
7977
|
+
@staticmethod
|
7978
|
+
def IsConnected(graph, vertexA, vertexB, silent: bool = False):
|
7979
|
+
"""
|
7980
|
+
Returns True if the two input vertices are directly connected by an edge. Returns False otherwise.
|
7981
|
+
|
7982
|
+
Parameters
|
7983
|
+
----------
|
7984
|
+
graph : topologic_core.Graph
|
7985
|
+
The input graph.
|
7986
|
+
vertexA : topologic_core.Vertex
|
7987
|
+
The first input vertex.
|
7988
|
+
vertexB : topologic_core.Vertex
|
7989
|
+
The second input vertex
|
7990
|
+
silent : bool , optional
|
7991
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
7992
|
+
|
7993
|
+
Returns
|
7994
|
+
-------
|
7995
|
+
bool
|
7996
|
+
True if the input vertices are connected by an edge. False otherwise.
|
7997
|
+
|
7998
|
+
"""
|
7999
|
+
from topologicpy.Topology import Topology
|
8000
|
+
|
8001
|
+
if not Topology.IsInstance(graph, "graph"):
|
8002
|
+
if not silent:
|
8003
|
+
print("Graph.IsConnected - Error: The input graph parameter is not a valid graph. Returning None.")
|
8004
|
+
return None
|
8005
|
+
|
8006
|
+
if not Topology.IsInstance(vertexA, "vertex"):
|
8007
|
+
if not silent:
|
8008
|
+
print("Graph.IsConnected - Error: The input vertexA parameter is not a valid vertex. Returning None.")
|
8009
|
+
return None
|
8010
|
+
|
8011
|
+
if not Topology.IsInstance(vertexB, "vertex"):
|
8012
|
+
if not silent:
|
8013
|
+
print("Graph.IsConnected - Error: The input vertexB parameter is not a valid vertex. Returning None.")
|
8014
|
+
return None
|
8015
|
+
|
8016
|
+
if vertexA == vertexB:
|
8017
|
+
if not silent:
|
8018
|
+
print("Graph.IsConnected - Warrning: The two input vertices are the same vertex. Returning False.")
|
8019
|
+
return False
|
8020
|
+
shortest_path = Graph.ShortestPath(graph, vertexA, vertexB)
|
8021
|
+
if shortest_path == None:
|
8022
|
+
return False
|
8023
|
+
else:
|
8024
|
+
edges = Topology.Edges(shortest_path)
|
8025
|
+
if len(edges) == 1:
|
8026
|
+
return True
|
8027
|
+
else:
|
8028
|
+
return False
|
8029
|
+
|
7514
8030
|
@staticmethod
|
7515
8031
|
def IsErdoesGallai(graph, sequence):
|
7516
8032
|
"""
|
@@ -7777,6 +8293,61 @@ class Graph:
|
|
7777
8293
|
json_string = json.dumps(json_data, indent=indent, sort_keys=sortKeys)
|
7778
8294
|
return json_string
|
7779
8295
|
|
8296
|
+
def Laplacian(graph, silent: bool = False, normalized: bool = False):
|
8297
|
+
"""
|
8298
|
+
Returns the Laplacian matrix of the input graph. See https://en.wikipedia.org/wiki/Laplacian_matrix.
|
8299
|
+
|
8300
|
+
Parameters
|
8301
|
+
----------
|
8302
|
+
graph : topologic_core.Graph
|
8303
|
+
The input graph.
|
8304
|
+
silent : bool , optional
|
8305
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
8306
|
+
normalized : bool , optional
|
8307
|
+
If set to True, the returned Laplacian matrix is normalized. The default is False.
|
8308
|
+
|
8309
|
+
Returns
|
8310
|
+
-------
|
8311
|
+
list
|
8312
|
+
The Laplacian matrix as a nested list.
|
8313
|
+
"""
|
8314
|
+
from topologicpy.Topology import Topology
|
8315
|
+
import numpy as np
|
8316
|
+
|
8317
|
+
if not Topology.IsInstance(graph, "graph"):
|
8318
|
+
if not silent:
|
8319
|
+
print("Graph.Laplacian - Error: The input graph parameter is not a valid graph. Returning None.")
|
8320
|
+
return None
|
8321
|
+
|
8322
|
+
# Get vertices of the graph
|
8323
|
+
vertices = Graph.Vertices(graph)
|
8324
|
+
n = len(vertices)
|
8325
|
+
|
8326
|
+
# Initialize Laplacian matrix
|
8327
|
+
laplacian = np.zeros((n, n))
|
8328
|
+
|
8329
|
+
# Fill Laplacian matrix
|
8330
|
+
for i, v1 in enumerate(vertices):
|
8331
|
+
for j, v2 in enumerate(vertices):
|
8332
|
+
if i == j:
|
8333
|
+
laplacian[i][j] = float(Graph.VertexDegree(graph, v1))
|
8334
|
+
elif Graph.IsConnected(graph, v1, v2):
|
8335
|
+
laplacian[i][j] = -1.0
|
8336
|
+
else:
|
8337
|
+
laplacian[i][j] = 0.0
|
8338
|
+
|
8339
|
+
# Normalize the Laplacian if requested
|
8340
|
+
if normalized:
|
8341
|
+
degree_matrix = np.diag(laplacian.diagonal())
|
8342
|
+
with np.errstate(divide='ignore'): # Suppress warnings for division by zero
|
8343
|
+
d_inv_sqrt = np.diag(1.0 / np.sqrt(degree_matrix.diagonal()))
|
8344
|
+
d_inv_sqrt[np.isinf(d_inv_sqrt)] = 0 # Replace infinities with zero
|
8345
|
+
|
8346
|
+
normalized_laplacian = d_inv_sqrt @ laplacian @ d_inv_sqrt
|
8347
|
+
return normalized_laplacian.tolist()
|
8348
|
+
|
8349
|
+
return laplacian.tolist()
|
8350
|
+
|
7780
8351
|
@staticmethod
|
7781
8352
|
def Leaves(graph, edgeKey: str = None, tolerance: float = 0.0001, silent: bool = False):
|
7782
8353
|
"""
|
@@ -8804,7 +9375,63 @@ class Graph:
|
|
8804
9375
|
d = Dictionary.SetValueAtKey(d, key, scores[i])
|
8805
9376
|
v = Topology.SetDictionary(v, d)
|
8806
9377
|
return scores
|
8807
|
-
|
9378
|
+
|
9379
|
+
@staticmethod
|
9380
|
+
def Partition(graph, method: str = "Betweenness", n: int = 2, m: int = 10, key: str ="partition",
|
9381
|
+
mantissa: int = 6, tolerance: float = 0.0001, silent: bool = False):
|
9382
|
+
"""
|
9383
|
+
Partitions the input graph based on the desired partition method. See https://en.wikipedia.org/wiki/Graph_partition.
|
9384
|
+
|
9385
|
+
Parameters
|
9386
|
+
----------
|
9387
|
+
graph : topologicp.Graph
|
9388
|
+
The input topologic graph.
|
9389
|
+
method : str , optional
|
9390
|
+
The desired partitioning method. The options are:
|
9391
|
+
- "Betweenness"
|
9392
|
+
- "Community" or "Louvain"
|
9393
|
+
- "Fiedler" or "Eigen"
|
9394
|
+
It is case insensitive. The default is "Betweenness"
|
9395
|
+
n : int , optional
|
9396
|
+
The desired number of partitions when selecting the "Betweenness" method. This parameter is ignored for other methods. The default is 2.
|
9397
|
+
m : int , optional
|
9398
|
+
The desired maximum number of tries to partition the graph when selecting the "Betweenness" method. This parameter is ignored for other methods. The default is 10.
|
9399
|
+
key : str , optional
|
9400
|
+
The vertex and edge dictionary key under which to store the parition number. The default is "partition".
|
9401
|
+
Valid partition numbers start from 1. Cut edges receive a partition number of 0.
|
9402
|
+
mantissa : int , optional
|
9403
|
+
The desired length of the mantissa. The default is 6.
|
9404
|
+
tolerance : float , optional
|
9405
|
+
The desired tolerance. The default is 0.0001.
|
9406
|
+
silent : bool , optional
|
9407
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
9408
|
+
|
9409
|
+
Returns
|
9410
|
+
-------
|
9411
|
+
topologicpy.Graph
|
9412
|
+
The partitioned topologic graph.
|
9413
|
+
|
9414
|
+
"""
|
9415
|
+
|
9416
|
+
m_d = Graph.MeshData(graph)
|
9417
|
+
new_graph = Graph.ByMeshData(vertices = m_d['vertices'],
|
9418
|
+
edges = m_d['edges'],
|
9419
|
+
vertexDictionaries = m_d['vertexDictionaries'],
|
9420
|
+
edgeDictionaries = m_d['edgeDictionaries'])
|
9421
|
+
if "between" in method.lower():
|
9422
|
+
_ = Graph.BetweennessPartition(new_graph, n=n, m=m, key=key, tolerance=tolerance, silent=silent)
|
9423
|
+
return new_graph
|
9424
|
+
elif "community" in method.lower() or "louvain" in method.lower():
|
9425
|
+
_ = Graph.CommunityPartition(new_graph, key=key, mantissa=mantissa, tolerance=tolerance, silent=silent)
|
9426
|
+
return new_graph
|
9427
|
+
elif "fied" in method.lower() or "eig" in method.lower():
|
9428
|
+
_ = Graph.FiedlerVectorPartition(new_graph, key=key, mantissa=mantissa, tolerance=tolerance, silent=silent)
|
9429
|
+
return new_graph
|
9430
|
+
else:
|
9431
|
+
if not silent:
|
9432
|
+
print("Graph.Partition - Error: The chosen method is not supported. Returning None.")
|
9433
|
+
return None
|
9434
|
+
|
8808
9435
|
@staticmethod
|
8809
9436
|
def Path(graph, vertexA, vertexB, tolerance=0.0001):
|
8810
9437
|
"""
|
@@ -9519,6 +10146,86 @@ class Graph:
|
|
9519
10146
|
return None
|
9520
10147
|
return len(Graph.Edges(graph))
|
9521
10148
|
|
10149
|
+
@staticmethod
|
10150
|
+
def Subgraph(graph, vertices, vertexKey="cutVertex", edgeKey="cutEdge", tolerance=0.0001, silent: bool = False):
|
10151
|
+
"""
|
10152
|
+
Returns a subgraph of the input graph as defined by the input vertices.
|
10153
|
+
|
10154
|
+
Parameters
|
10155
|
+
----------
|
10156
|
+
graph : topologic_core.Graph
|
10157
|
+
The input graph.
|
10158
|
+
vertexKey : str , optional
|
10159
|
+
The dictionary key under which to store the cut vertex status of each vertex. See https://en.wikipedia.org/wiki/Cut_(graph_theory).
|
10160
|
+
vertex cuts are indicated with a value of 1. The default is "cutVertex".
|
10161
|
+
edgeKey : str , optional
|
10162
|
+
The dictionary key under which to store the cut edge status of each edge. See https://en.wikipedia.org/wiki/Cut_(graph_theory).
|
10163
|
+
edge cuts are indicated with a value of 1. The default is "cutVertex".
|
10164
|
+
tolerance : float , optional
|
10165
|
+
The desired tolerance. The default is 0.0001.
|
10166
|
+
silent : bool , optional
|
10167
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
10168
|
+
|
10169
|
+
Returns
|
10170
|
+
-------
|
10171
|
+
topologic_core.Graph
|
10172
|
+
The created subgraph.
|
10173
|
+
|
10174
|
+
"""
|
10175
|
+
from topologicpy.Vertex import Vertex
|
10176
|
+
from topologicpy.Edge import Edge
|
10177
|
+
from topologicpy.Dictionary import Dictionary
|
10178
|
+
from topologicpy.Topology import Topology
|
10179
|
+
|
10180
|
+
if not Topology.IsInstance(graph, "graph"):
|
10181
|
+
if not silent:
|
10182
|
+
print("Graph.Subgraph - Error: The input graph parameter is not a valid graph. Returning None.")
|
10183
|
+
return None
|
10184
|
+
|
10185
|
+
if not isinstance(vertices, list):
|
10186
|
+
if not silent:
|
10187
|
+
print("Graph.Subgraph - Error: The input vertices parameter is not a valid list. Returning None.")
|
10188
|
+
return None
|
10189
|
+
|
10190
|
+
vertex_list = [v for v in vertices if Topology.IsInstance(v, "vertex")]
|
10191
|
+
if len(vertex_list) < 1:
|
10192
|
+
if not silent:
|
10193
|
+
print("Graph.Subgraph - Error: The input vertices parameter does not contain any valid vertices. Returning None.")
|
10194
|
+
return None
|
10195
|
+
|
10196
|
+
edges = Graph.Edges(graph, vertices=vertex_list)
|
10197
|
+
# Set the vertexCut status to 0 for all input vertices
|
10198
|
+
for v in vertex_list:
|
10199
|
+
d = Topology.Dictionary(v)
|
10200
|
+
d = Dictionary.SetValueAtKey(d, vertexKey, 0)
|
10201
|
+
v = Topology.SetDictionary(v, d)
|
10202
|
+
|
10203
|
+
final_edges = []
|
10204
|
+
if not edges == None:
|
10205
|
+
for edge in edges:
|
10206
|
+
sv = Edge.StartVertex(edge)
|
10207
|
+
status_1 = any([Vertex.IsCoincident(sv, v, tolerance=tolerance) for v in vertices])
|
10208
|
+
ev = Edge.EndVertex(edge)
|
10209
|
+
status_2 = any([Vertex.IsCoincident(ev, v, tolerance=tolerance) for v in vertices])
|
10210
|
+
if status_1 and status_2:
|
10211
|
+
cutEdge = 0
|
10212
|
+
else:
|
10213
|
+
cutEdge = 1
|
10214
|
+
d = Topology.Dictionary(edge)
|
10215
|
+
d = Dictionary.SetValueAtKey(d, edgeKey, cutEdge)
|
10216
|
+
edge = Topology.SetDictionary(edge, d)
|
10217
|
+
final_edges.append(edge)
|
10218
|
+
return_graph = Graph.ByVerticesEdges(vertex_list, final_edges)
|
10219
|
+
graph_vertices = Graph.Vertices(return_graph)
|
10220
|
+
# Any vertex in the final graph that does not have a vertexCut of 0 is a new vertex and as such needs to have a vertexCut of 1.
|
10221
|
+
for v in graph_vertices:
|
10222
|
+
d = Topology.Dictionary(v)
|
10223
|
+
value = Dictionary.ValueAtKey(d, vertexKey)
|
10224
|
+
if not value == 0:
|
10225
|
+
d = Dictionary.SetValueAtKey(d, vertexKey, 1)
|
10226
|
+
v = Topology.SetDictionary(v, d)
|
10227
|
+
return return_graph
|
10228
|
+
|
9522
10229
|
@staticmethod
|
9523
10230
|
def _topological_distance(g, start, target):
|
9524
10231
|
from collections import deque
|
topologicpy/Matrix.py
CHANGED
@@ -164,6 +164,69 @@ class Matrix:
|
|
164
164
|
[0,0,1,0],
|
165
165
|
[translateX,translateY,translateZ,1]]
|
166
166
|
|
167
|
+
@staticmethod
|
168
|
+
def EigenvaluesAndVectors(matrix, mantissa: int = 6, silent: bool = False):
|
169
|
+
import numpy as np
|
170
|
+
"""
|
171
|
+
Returns the eigenvalues and eigenvectors of the input matrix. See https://en.wikipedia.org/wiki/Eigenvalues_and_eigenvectors
|
172
|
+
|
173
|
+
Parameters
|
174
|
+
----------
|
175
|
+
matrix : list
|
176
|
+
The input matrix. Assumed to be a laplacian matrix.
|
177
|
+
mantissa : int , optional
|
178
|
+
The desired length of the mantissa. The default is 6.
|
179
|
+
silent : bool , optional
|
180
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
181
|
+
|
182
|
+
Returns
|
183
|
+
-------
|
184
|
+
list
|
185
|
+
The list of eigenvalues and eigenvectors of the input matrix.
|
186
|
+
|
187
|
+
"""
|
188
|
+
from topologicpy.Helper import Helper
|
189
|
+
import numpy as np
|
190
|
+
|
191
|
+
if not isinstance(matrix, list):
|
192
|
+
if not silent:
|
193
|
+
print("Matrix.Eigenvalues - Error: The input matrix parameter is not a valid matrix. Returning None.")
|
194
|
+
return None
|
195
|
+
|
196
|
+
np_matrix = np.array(matrix)
|
197
|
+
if not isinstance(np_matrix, np.ndarray):
|
198
|
+
if not silent:
|
199
|
+
print("Matrix.Eigenvalues - Error: The input matrix parameter is not a valid matrix. Returning None.")
|
200
|
+
return None
|
201
|
+
|
202
|
+
# Square check
|
203
|
+
if np_matrix.shape[0] != np_matrix.shape[1]:
|
204
|
+
if not silent:
|
205
|
+
print("Matrix.Eigenvalues - Error: The input matrix parameter is not a square matrix. Returning None.")
|
206
|
+
return None
|
207
|
+
|
208
|
+
# Symmetry check
|
209
|
+
if not np.allclose(np_matrix, np_matrix.T):
|
210
|
+
if not silent:
|
211
|
+
print("Matrix.Eigenvalues - Error: The input matrix is not symmetric. Returning None.")
|
212
|
+
return None
|
213
|
+
|
214
|
+
# # Degree matrix
|
215
|
+
# degree_matrix = np.diag(np_matrix.sum(axis=1))
|
216
|
+
|
217
|
+
# # Laplacian matrix
|
218
|
+
# laplacian_matrix = degree_matrix - np_matrix
|
219
|
+
|
220
|
+
# Eigenvalues
|
221
|
+
eigenvalues, eigenvectors = np.linalg.eig(np_matrix)
|
222
|
+
|
223
|
+
e_values = [round(x, mantissa) for x in list(np.sort(eigenvalues))]
|
224
|
+
e_vectors = []
|
225
|
+
for eigenvector in eigenvectors:
|
226
|
+
e_vectors.append([round(x, mantissa) for x in eigenvector])
|
227
|
+
e_vectors = Helper.Sort(e_vectors, list(eigenvalues))
|
228
|
+
return e_values, e_vectors
|
229
|
+
|
167
230
|
@staticmethod
|
168
231
|
def Multiply(matA, matB):
|
169
232
|
"""
|
topologicpy/Vertex.py
CHANGED
@@ -1710,6 +1710,89 @@ class Vertex():
|
|
1710
1710
|
pt = project_point_onto_plane(Vertex.Coordinates(vertex), [eq["a"], eq["b"], eq["c"], eq["d"]], direction)
|
1711
1711
|
return Vertex.ByCoordinates(pt[0], pt[1], pt[2])
|
1712
1712
|
|
1713
|
+
@staticmethod
|
1714
|
+
def Separate(*vertices, minDistance: float = 0.0001, silent: bool = False):
|
1715
|
+
"""
|
1716
|
+
Separates the input vertices such that no two vertices are within the input minimum distance.
|
1717
|
+
|
1718
|
+
Parameters
|
1719
|
+
----------
|
1720
|
+
vertices : *topologicpy.Vertex
|
1721
|
+
One or more instances of a topologic vertex to be processed.
|
1722
|
+
minDistance : float , optional
|
1723
|
+
The desired minimum distance. The default is 0.0001.
|
1724
|
+
silent : bool , optional
|
1725
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
1726
|
+
|
1727
|
+
Returns
|
1728
|
+
-------
|
1729
|
+
list
|
1730
|
+
The list of vertices with adjusted positions
|
1731
|
+
|
1732
|
+
"""
|
1733
|
+
from topologicpy.Topology import Topology
|
1734
|
+
from topologicpy.Helper import Helper
|
1735
|
+
from topologicpy.Dictionary import Dictionary
|
1736
|
+
from math import sqrt
|
1737
|
+
from scipy.spatial import KDTree
|
1738
|
+
import numpy as np
|
1739
|
+
|
1740
|
+
if len(vertices) == 0:
|
1741
|
+
if not silent:
|
1742
|
+
print("Vertex.Separate - Error: The input vertices parameter is an empty list. Returning None.")
|
1743
|
+
return None
|
1744
|
+
if len(vertices) == 1:
|
1745
|
+
vertices = vertices[0]
|
1746
|
+
if isinstance(vertices, list):
|
1747
|
+
if len(vertices) == 0:
|
1748
|
+
if not silent:
|
1749
|
+
print("Vertex.Separate - Error: The input vertices parameter is an empty list. Returning None.")
|
1750
|
+
return None
|
1751
|
+
else:
|
1752
|
+
vertexList = [x for x in vertices if Topology.IsInstance(x, "Vertex")]
|
1753
|
+
if len(vertexList) == 0:
|
1754
|
+
if not silent:
|
1755
|
+
print("Vertex.Separate - Error: The input vertices parameter does not contain any valid vertices. Returning None.")
|
1756
|
+
return None
|
1757
|
+
else:
|
1758
|
+
if not silent:
|
1759
|
+
print("Vertex.Separate - Warning: The input vertices parameter contains only one vertex. Returning the same vertex.")
|
1760
|
+
return vertices
|
1761
|
+
else:
|
1762
|
+
vertexList = Helper.Flatten(list(vertices))
|
1763
|
+
vertexList = [x for x in vertexList if Topology.IsInstance(x, "Vertex")]
|
1764
|
+
if len(vertexList) == 0:
|
1765
|
+
if not silent:
|
1766
|
+
print("Vertex.Separate - Error: The input parameters do not contain any valid vertices. Returning None.")
|
1767
|
+
return None
|
1768
|
+
|
1769
|
+
coords = np.array([[v.X(), v.Y(), v.Z()] for v in vertexList]) # Extract coordinates
|
1770
|
+
tree = KDTree(coords) # Build k-d tree for efficient neighbor search
|
1771
|
+
|
1772
|
+
for i, vertex in enumerate(coords):
|
1773
|
+
neighbors = tree.query_ball_point(vertex, minDistance)
|
1774
|
+
for neighbor_index in neighbors:
|
1775
|
+
if neighbor_index != i: # Avoid self-comparison
|
1776
|
+
direction = coords[neighbor_index] - vertex
|
1777
|
+
distance = np.linalg.norm(direction)
|
1778
|
+
if distance < minDistance:
|
1779
|
+
# Move current vertex away from its neighbor
|
1780
|
+
adjustment = (minDistance - distance) / 2
|
1781
|
+
unit_vector = direction / distance if distance != 0 else np.random.rand(3)
|
1782
|
+
coords[i] -= unit_vector * adjustment
|
1783
|
+
coords[neighbor_index] += unit_vector * adjustment
|
1784
|
+
|
1785
|
+
# Rebuild the k-d tree after adjustment
|
1786
|
+
tree = KDTree(coords)
|
1787
|
+
|
1788
|
+
# Convert adjusted coordinates back to Vertex objects
|
1789
|
+
separated_vertices = [Vertex.ByCoordinates(x, y, z) for x, y, z in coords]
|
1790
|
+
for i, vertex in enumerate(vertexList):
|
1791
|
+
d = Topology.Dictionary(vertex)
|
1792
|
+
if len(Dictionary.Keys(d)) > 0:
|
1793
|
+
separated_vertices[i] = Topology.SetDictionary(separated_vertices[i], d)
|
1794
|
+
return separated_vertices
|
1795
|
+
|
1713
1796
|
@staticmethod
|
1714
1797
|
def X(vertex, mantissa: int = 6) -> float:
|
1715
1798
|
"""
|
topologicpy/version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = '0.7.
|
1
|
+
__version__ = '0.7.99'
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: topologicpy
|
3
|
-
Version: 0.7.
|
3
|
+
Version: 0.7.99
|
4
4
|
Summary: An AI-Powered Spatial Modelling and Analysis Software Library for Architecture, Engineering, and Construction.
|
5
5
|
Author-email: Wassim Jabi <wassim.jabi@gmail.com>
|
6
6
|
License: AGPL v3 License
|
@@ -11,11 +11,11 @@ topologicpy/Dictionary.py,sha256=t0O7Du-iPq46FyKqZfcjHfsUK1E8GS_e67R2V5cpkbw,331
|
|
11
11
|
topologicpy/Edge.py,sha256=gaLqyjFOqFHpw69Ftr4rc-kvakYpauQwhOK4ZO-V35g,67287
|
12
12
|
topologicpy/EnergyModel.py,sha256=UoQ9Jm-hYsN383CbcLKw-y6BKitRHj0uyh84yQ-8ACg,53856
|
13
13
|
topologicpy/Face.py,sha256=wczXpMcfub8Eb10lA4rrXksvi5YYCbRjBdp3lOTUwK0,172618
|
14
|
-
topologicpy/Graph.py,sha256
|
14
|
+
topologicpy/Graph.py,sha256=-vB47i0s7fbh6pTQGuiJujm1tM-ubTuUeyjJeQ8iAEo,492172
|
15
15
|
topologicpy/Grid.py,sha256=2s9cSlWldivn1i9EUz4OOokJyANveqmRe_vR93CAndI,18245
|
16
16
|
topologicpy/Helper.py,sha256=F3h4_qcOD_PHAoVe0tEbEE7_jYyVcaHjtwVs4QHOZuI,23978
|
17
17
|
topologicpy/Honeybee.py,sha256=Y_El6M8x3ixvvIe_VcRiwj_4C89ZZg5_WlT7adbCkpw,21849
|
18
|
-
topologicpy/Matrix.py,sha256=
|
18
|
+
topologicpy/Matrix.py,sha256=tiPum1gTvkKxOyxHBDviH4BwLbdlAusBwMe7ZZfu6Po,10510
|
19
19
|
topologicpy/Neo4j.py,sha256=BKOF29fRgXmdpMGkrNzuYbyqgCJ6ElPPMYlfTxXiVbc,22392
|
20
20
|
topologicpy/Plotly.py,sha256=Tvo0_zKVEHtPhsMNNvLy5G0HIys5FPAOyp_o4QN_I_A,115760
|
21
21
|
topologicpy/Polyskel.py,sha256=EFsuh2EwQJGPLiFUjvtXmAwdX-A4r_DxP5hF7Qd3PaU,19829
|
@@ -25,12 +25,12 @@ topologicpy/Speckle.py,sha256=AlsGlSDuKRtX5jhVsPNSSjjbZis079HbUchDH_5RJmE,18187
|
|
25
25
|
topologicpy/Sun.py,sha256=42tDWMYpwRG7Z2Qjtp94eRgBuqySq7k8TgNUZDK7QxQ,36837
|
26
26
|
topologicpy/Topology.py,sha256=kAnJrVyrwJX8c-C4q1cewJ80byG8uaoBWUuk0T6U4SY,441788
|
27
27
|
topologicpy/Vector.py,sha256=Cl7besf20cAGmyNPh-9gbFAHnRU5ZWSMChJ3VyFIDs4,35416
|
28
|
-
topologicpy/Vertex.py,sha256=
|
28
|
+
topologicpy/Vertex.py,sha256=tv6C-rbuNgXHDGgVLT5fbalynLdXqlUuiCDKtkeQ0vk,77814
|
29
29
|
topologicpy/Wire.py,sha256=bX8wO96gFa7HZPY0CFlmYQBOUP_1e0jCb02BPxaY-ao,222981
|
30
30
|
topologicpy/__init__.py,sha256=vlPCanUbxe5NifC4pHcnhSzkmmYcs_UrZrTlVMsxcFs,928
|
31
|
-
topologicpy/version.py,sha256=
|
32
|
-
topologicpy-0.7.
|
33
|
-
topologicpy-0.7.
|
34
|
-
topologicpy-0.7.
|
35
|
-
topologicpy-0.7.
|
36
|
-
topologicpy-0.7.
|
31
|
+
topologicpy/version.py,sha256=gGhfskMFYv1QhkPM9mcpO5BWm5CMyWE6f2zQSx3Zec4,23
|
32
|
+
topologicpy-0.7.99.dist-info/LICENSE,sha256=FK0vJ73LuE8PYJAn7LutsReWR47-Ooovw2dnRe5yV6Q,681
|
33
|
+
topologicpy-0.7.99.dist-info/METADATA,sha256=Mzw982237903EfiTROFP2zAKuxkY8RIBQ1GjJdJQ-9s,10513
|
34
|
+
topologicpy-0.7.99.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
35
|
+
topologicpy-0.7.99.dist-info/top_level.txt,sha256=J30bDzW92Ob7hw3zA8V34Jlp-vvsfIkGzkr8sqvb4Uw,12
|
36
|
+
topologicpy-0.7.99.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|