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 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 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.
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 calculate_betweenness():
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
- vertices = Graph.Vertices(graph)
1354
+ if "v" in method.lower():
1355
+ vertices = Graph.Vertices(graph)
1303
1356
 
1304
- if len(vertices) < 1:
1305
- if not silent:
1306
- print("Graph.BetweenessCentrality - Error: The input graph does not contain valid vertices. Returning None.")
1307
- return None
1308
- if len(vertices) == 1:
1309
- d = Topology.Dictionary(vertices[0])
1310
- d = Dictionary.SetValueAtKey(d, key, 1.0)
1311
- vertices[0] = Topology.SetDictionary(vertices[0], d)
1312
- return [1.0]
1313
-
1314
- py_graph = Graph.AdjacencyDictionary(graph)
1315
- betweenness = calculate_betweenness()
1316
- for v in betweenness:
1317
- betweenness[v] /= 2.0 # Each shortest path is counted twice
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
- max_betweenness = max(betweenness.values())
1320
- if max_betweenness > 0:
1321
- for v in betweenness:
1322
- betweenness[v] /= max_betweenness # Normalize to [0, 1]
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
- edges = Graph.Edges(graph)
1380
- if len(edges) < 1:
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, 1)
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
- graph_edges = mesh_data['edges']
1389
- ig_graph = ig.Graph(edges=graph_edges)
1527
+ mesh_edges = mesh_data['edges']
1390
1528
 
1391
- bridges = ig_graph.bridges()
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 i, edge in enumerate(edges):
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
- if i in bridges:
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
- v = Topology.SetDictionary(v, dictionaries[i])
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 = Topology.Filter(vertices, searchType="equal to", key=labelKey, value=v)['filtered'][0]
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
- i_edges = Graph.Edges(graph, i_verts)
4264
- lengths.append(len(i_verts))
4265
- g_component = Graph.ByVerticesEdges(i_verts, i_edges)
4266
- return_components.append(g_component)
4267
- return_components = Helper.Sort(return_components, lengths)
4268
- return_components.reverse()
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 = "community", mantissa: int = 6, tolerance: float = 0.0001, silent: bool = False):
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 closeness centrality score. The default is "community".
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 created topologic graph.
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
- community_list = communities.membership
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, community_list[i])
4832
+ d = Dictionary.SetValueAtKey(d, key, partition_list[i]+1)
4523
4833
  v = Topology.SetDictionary(v, d)
4524
- return community_list
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.97'
1
+ __version__ = '0.7.99'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: topologicpy
3
- Version: 0.7.97
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=tAm-kvvHAgadPJIcXvqgEoMuAdAo3RS3mu9uhhgx7TI,460497
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=umgR7An919-wGInXJ1wpqnoQ2jCPdyMe2rcWTZ16upk,8079
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=QkeNPFTX-adKhEHMole0et9FCy0xXmTHVcmsYqqotSw,73904
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=eF2oD9YC4qAbKpy4BgX03mi3PRuOcHb_V4K8lN5jAn0,23
32
- topologicpy-0.7.97.dist-info/LICENSE,sha256=FK0vJ73LuE8PYJAn7LutsReWR47-Ooovw2dnRe5yV6Q,681
33
- topologicpy-0.7.97.dist-info/METADATA,sha256=pingqvbKxBUf-eX6CrZQCoIyEV1tduXqvZwpol4DDuQ,10513
34
- topologicpy-0.7.97.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
35
- topologicpy-0.7.97.dist-info/top_level.txt,sha256=J30bDzW92Ob7hw3zA8V34Jlp-vvsfIkGzkr8sqvb4Uw,12
36
- topologicpy-0.7.97.dist-info/RECORD,,
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,,