topologicpy 0.7.90__py3-none-any.whl → 0.7.92__py3-none-any.whl

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