topologicpy 0.7.87__py3-none-any.whl → 0.7.91__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/Cell.py +7 -3
- topologicpy/Face.py +175 -18
- topologicpy/Graph.py +765 -114
- topologicpy/Plotly.py +43 -22
- topologicpy/Topology.py +19 -16
- topologicpy/version.py +1 -1
- {topologicpy-0.7.87.dist-info → topologicpy-0.7.91.dist-info}/METADATA +1 -1
- {topologicpy-0.7.87.dist-info → topologicpy-0.7.91.dist-info}/RECORD +11 -11
- {topologicpy-0.7.87.dist-info → topologicpy-0.7.91.dist-info}/LICENSE +0 -0
- {topologicpy-0.7.87.dist-info → topologicpy-0.7.91.dist-info}/WHEEL +0 -0
- {topologicpy-0.7.87.dist-info → topologicpy-0.7.91.dist-info}/top_level.txt +0 -0
topologicpy/Graph.py
CHANGED
@@ -1328,7 +1328,113 @@ class Graph:
|
|
1328
1328
|
return bot_graph.serialize(format=format)
|
1329
1329
|
|
1330
1330
|
@staticmethod
|
1331
|
-
def BetweenessCentrality(graph,
|
1331
|
+
def BetweenessCentrality(graph, key: str = "betweeness_centrality", mantissa: int = 6, tolerance: float = 0.001, silent: bool = False):
|
1332
|
+
"""
|
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.
|
1334
|
+
|
1335
|
+
Parameters
|
1336
|
+
----------
|
1337
|
+
graph : topologic_core.Graph
|
1338
|
+
The input graph.
|
1339
|
+
key : str , optional
|
1340
|
+
The dictionary key under which to save the betweeness centrality score. The default is "betweneess_centrality".
|
1341
|
+
mantissa : int , optional
|
1342
|
+
The desired length of the mantissa. The default is 6.
|
1343
|
+
tolerance : float , optional
|
1344
|
+
The desired tolerance. The default is 0.0001.
|
1345
|
+
|
1346
|
+
Returns
|
1347
|
+
-------
|
1348
|
+
list
|
1349
|
+
The betweeness centrality of the input list of vertices within the input graph. The values are in the range 0 to 1.
|
1350
|
+
|
1351
|
+
"""
|
1352
|
+
def vertex_betweenness_centrality(graph, vertices):
|
1353
|
+
"""
|
1354
|
+
Compute the betweenness centrality for vertices in the given TopologicPy graph.
|
1355
|
+
|
1356
|
+
Args:
|
1357
|
+
graph: The TopologicPy Graph object.
|
1358
|
+
|
1359
|
+
Returns:
|
1360
|
+
dict: A dictionary mapping each vertex to its betweenness centrality value.
|
1361
|
+
"""
|
1362
|
+
from collections import defaultdict
|
1363
|
+
n = len(vertices)
|
1364
|
+
idList = []
|
1365
|
+
vertex_map = {}
|
1366
|
+
for i, v in enumerate(vertices):
|
1367
|
+
d = Topology.Dictionary(v)
|
1368
|
+
d = Dictionary.SetValueAtKey(d, "__id_", str(i))
|
1369
|
+
v = Topology.SetDictionary(v, d)
|
1370
|
+
idList.append(str(i))
|
1371
|
+
vertex_map[str(i)] = v
|
1372
|
+
if n < 2:
|
1373
|
+
return {v: 0.0 for v in idList}
|
1374
|
+
centrality = defaultdict(float)
|
1375
|
+
|
1376
|
+
for source in idList:
|
1377
|
+
stack, paths, sigma = [], {}, {v: 0.0 for v in idList}
|
1378
|
+
sigma[source] = 1.0
|
1379
|
+
paths[source] = []
|
1380
|
+
|
1381
|
+
queue = [source]
|
1382
|
+
while queue:
|
1383
|
+
v = queue.pop(0)
|
1384
|
+
stack.append(v)
|
1385
|
+
vertex = vertex_map[v]
|
1386
|
+
for neighbor in Graph.AdjacentVertices(graph, vertex):
|
1387
|
+
d = Topology.Dictionary(neighbor)
|
1388
|
+
neighbor_id = Dictionary.ValueAtKey(d, "__id_")
|
1389
|
+
if neighbor_id not in paths:
|
1390
|
+
queue.append(neighbor_id)
|
1391
|
+
paths[neighbor_id] = [v]
|
1392
|
+
elif v not in paths[neighbor_id]:
|
1393
|
+
paths[neighbor_id].append(v)
|
1394
|
+
sigma[neighbor_id] += sigma[v]
|
1395
|
+
|
1396
|
+
delta = {v: 0.0 for v in idList}
|
1397
|
+
while stack:
|
1398
|
+
w = stack.pop()
|
1399
|
+
for v in paths.get(w, []):
|
1400
|
+
delta[v] += (sigma[v] / sigma[w]) * (1 + delta[w])
|
1401
|
+
if w != source:
|
1402
|
+
centrality[w] += delta[w]
|
1403
|
+
# Normalize centrality values
|
1404
|
+
max_centrality = max([centrality[v] for v in idList])
|
1405
|
+
min_centrality = min([centrality[v] for v in idList])
|
1406
|
+
centrality = [round((centrality[v]-min_centrality)/max_centrality, mantissa) for v in idList]
|
1407
|
+
for i, v in enumerate(vertices):
|
1408
|
+
d = Topology.Dictionary(v)
|
1409
|
+
d = Dictionary.SetValueAtKey(d, "betweeness_centrality", centrality[i])
|
1410
|
+
d = Dictionary.RemoveKey(d, "__id_")
|
1411
|
+
v = Topology.SetDictionary(v, d)
|
1412
|
+
return centrality
|
1413
|
+
|
1414
|
+
from topologicpy.Topology import Topology
|
1415
|
+
from topologicpy.Dictionary import Dictionary
|
1416
|
+
|
1417
|
+
if not Topology.IsInstance(graph, "Graph"):
|
1418
|
+
if not silent:
|
1419
|
+
print("Graph.BetweenessCentrality - Error: The input graph is not a valid graph. Returning None.")
|
1420
|
+
return None
|
1421
|
+
|
1422
|
+
vertices = Graph.Vertices(graph)
|
1423
|
+
|
1424
|
+
if len(vertices) < 1:
|
1425
|
+
if not silent:
|
1426
|
+
print("Graph.BetweenessCentrality - Error: The input graph does not contain valid vertices. Returning None.")
|
1427
|
+
return None
|
1428
|
+
if len(vertices) == 1:
|
1429
|
+
d = Topology.Dictionary(vertices[0])
|
1430
|
+
d = Dictionary.SetValueAtKey(d, key, 1.0)
|
1431
|
+
vertices[0] = Topology.SetDictionary(vertices[0], d)
|
1432
|
+
return [1.0]
|
1433
|
+
|
1434
|
+
return vertex_betweenness_centrality(graph, vertices)
|
1435
|
+
|
1436
|
+
@staticmethod
|
1437
|
+
def BetweenessCentrality_old(graph, vertices=None, sources=None, destinations=None, key: str = "betweeness_centrality", mantissa: int = 6, tolerance: float = 0.001):
|
1332
1438
|
"""
|
1333
1439
|
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.
|
1334
1440
|
|
@@ -2160,9 +2266,14 @@ class Graph:
|
|
2160
2266
|
return {'graphs':graphs, 'labels':labels}
|
2161
2267
|
|
2162
2268
|
@staticmethod
|
2163
|
-
def ByIFCFile(file,
|
2164
|
-
|
2165
|
-
|
2269
|
+
def ByIFCFile(file,
|
2270
|
+
includeTypes: list = [],
|
2271
|
+
excludeTypes: list = [],
|
2272
|
+
includeRels: list = [],
|
2273
|
+
excludeRels: list = [],
|
2274
|
+
transferDictionaries: bool = False,
|
2275
|
+
useInternalVertex: bool = False,
|
2276
|
+
storeBREP: bool = False,
|
2166
2277
|
removeCoplanarFaces: bool = False,
|
2167
2278
|
xMin: float = -0.5, yMin: float = -0.5, zMin: float = -0.5,
|
2168
2279
|
xMax: float = 0.5, yMax: float = 0.5, zMax: float = 0.5,
|
@@ -2184,6 +2295,8 @@ class Graph:
|
|
2184
2295
|
A list of IFC relationship types to exclude from the graph. The default is [] which mean no relationship type is excluded.
|
2185
2296
|
transferDictionaries : bool , optional
|
2186
2297
|
If set to True, the dictionaries from the IFC file will be transferred to the topology. Otherwise, they won't. The default is False.
|
2298
|
+
useInternalVertex : bool , optional
|
2299
|
+
If set to True, use an internal vertex to represent the subtopology. Otherwise, use its centroid. The default is False.
|
2187
2300
|
storeBREP : bool , optional
|
2188
2301
|
If set to True, store the BRep of the subtopology in its representative vertex. The default is False.
|
2189
2302
|
removeCoplanarFaces : bool , optional
|
@@ -2466,7 +2579,7 @@ class Graph:
|
|
2466
2579
|
pset_python_dict = get_psets(ifc_object)
|
2467
2580
|
pset_dict = Dictionary.ByPythonDictionary(pset_python_dict)
|
2468
2581
|
topology_dict = Dictionary.ByMergedDictionaries([topology_dict, pset_dict])
|
2469
|
-
if storeBREP == True:
|
2582
|
+
if storeBREP == True or useInternalVertex == True:
|
2470
2583
|
shape_topology = None
|
2471
2584
|
if hasattr(ifc_object, "Representation") and ifc_object.Representation:
|
2472
2585
|
for rep in ifc_object.Representation.Representations:
|
@@ -2484,8 +2597,10 @@ class Graph:
|
|
2484
2597
|
if not shape_topology == None:
|
2485
2598
|
if removeCoplanarFaces == True:
|
2486
2599
|
shape_topology = Topology.RemoveCoplanarFaces(shape_topology, epsilon=0.0001)
|
2487
|
-
if not shape_topology == None:
|
2600
|
+
if not shape_topology == None and storeBREP:
|
2488
2601
|
topology_dict = Dictionary.SetValuesAtKeys(topology_dict, ["brep", "brepType", "brepTypeString"], [Topology.BREPString(shape_topology), Topology.Type(shape_topology), Topology.TypeAsString(shape_topology)])
|
2602
|
+
if not shape_topology == None and useInternalVertex == True:
|
2603
|
+
centroid = Topology.InternalVertex(shape_topology)
|
2489
2604
|
centroid = Topology.SetDictionary(centroid, topology_dict)
|
2490
2605
|
return centroid
|
2491
2606
|
return None
|
@@ -2571,7 +2686,16 @@ class Graph:
|
|
2571
2686
|
return g
|
2572
2687
|
|
2573
2688
|
@staticmethod
|
2574
|
-
def ByIFCPath(path,
|
2689
|
+
def ByIFCPath(path,
|
2690
|
+
includeTypes=[],
|
2691
|
+
excludeTypes=[],
|
2692
|
+
includeRels=[],
|
2693
|
+
excludeRels=[],
|
2694
|
+
transferDictionaries=False,
|
2695
|
+
useInternalVertex=False,
|
2696
|
+
storeBREP=False,
|
2697
|
+
removeCoplanarFaces=False,
|
2698
|
+
xMin=-0.5, yMin=-0.5, zMin=-0.5, xMax=0.5, yMax=0.5, zMax=0.5):
|
2575
2699
|
"""
|
2576
2700
|
Create a Graph from an IFC path. This code is partially based on code from Bruno Postle.
|
2577
2701
|
|
@@ -2589,6 +2713,8 @@ class Graph:
|
|
2589
2713
|
A list of IFC relationship types to exclude from the graph. The default is [] which mean no relationship type is excluded.
|
2590
2714
|
transferDictionaries : bool , optional
|
2591
2715
|
If set to True, the dictionaries from the IFC file will be transferred to the topology. Otherwise, they won't. The default is False.
|
2716
|
+
useInternalVertex : bool , optional
|
2717
|
+
If set to True, use an internal vertex to represent the subtopology. Otherwise, use its centroid. The default is False.
|
2592
2718
|
storeBREP : bool , optional
|
2593
2719
|
If set to True, store the BRep of the subtopology in its representative vertex. The default is False.
|
2594
2720
|
removeCoplanarFaces : bool , optional
|
@@ -2647,6 +2773,7 @@ class Graph:
|
|
2647
2773
|
includeRels=includeRels,
|
2648
2774
|
excludeRels=excludeRels,
|
2649
2775
|
transferDictionaries=transferDictionaries,
|
2776
|
+
useInternalVertex=useInternalVertex,
|
2650
2777
|
storeBREP=storeBREP,
|
2651
2778
|
removeCoplanarFaces=removeCoplanarFaces,
|
2652
2779
|
xMin=xMin, yMin=yMin, zMin=zMin, xMax=xMax, yMax=yMax, zMax=zMax)
|
@@ -2708,7 +2835,82 @@ class Graph:
|
|
2708
2835
|
g_e = Topology.SetDictionary(g_e, d)
|
2709
2836
|
g_edges.append(g_e)
|
2710
2837
|
return Graph.ByVerticesEdges(g_vertices, g_edges)
|
2711
|
-
|
2838
|
+
|
2839
|
+
@staticmethod
|
2840
|
+
def ByNetworkXGraph(nxGraph, xKey="x", yKey="y", zKey="z", range=(-1, 1), mantissa: int = 6, tolerance: float = 0.0001):
|
2841
|
+
"""
|
2842
|
+
Converts the input NetworkX graph into a topologic Graph. See http://networkx.org
|
2843
|
+
|
2844
|
+
Parameters
|
2845
|
+
----------
|
2846
|
+
nxGraph : NetworkX graph
|
2847
|
+
The input NetworkX graph.
|
2848
|
+
xKey : str , optional
|
2849
|
+
The dictionary key under which to find the X-Coordinate of the vertex. The default is 'x'.
|
2850
|
+
yKey : str , optional
|
2851
|
+
The dictionary key under which to find the Y-Coordinate of the vertex. The default is 'y'.
|
2852
|
+
zKey : str , optional
|
2853
|
+
The dictionary key under which to find the Z-Coordinate of the vertex. The default is 'z'.
|
2854
|
+
range : tuple , optional
|
2855
|
+
The range to use for position coordinates if no values are found in the dictionaries. The default is (-1,1)
|
2856
|
+
mantissa : int , optional
|
2857
|
+
The desired length of the mantissa. The default is 6.
|
2858
|
+
tolerance : float , optional
|
2859
|
+
The desired tolerance. The default is 0.0001.
|
2860
|
+
|
2861
|
+
Returns
|
2862
|
+
-------
|
2863
|
+
topologicpy.Graph
|
2864
|
+
The created topologic graph.
|
2865
|
+
|
2866
|
+
"""
|
2867
|
+
from topologicpy.Vertex import Vertex
|
2868
|
+
from topologicpy.Edge import Edge
|
2869
|
+
from topologicpy.Topology import Topology
|
2870
|
+
from topologicpy.Dictionary import Dictionary
|
2871
|
+
|
2872
|
+
import random
|
2873
|
+
import numpy as np
|
2874
|
+
|
2875
|
+
# Create a mapping from NetworkX nodes to TopologicPy vertices
|
2876
|
+
nx_to_topologic_vertex = {}
|
2877
|
+
|
2878
|
+
# Create TopologicPy vertices for each node in the NetworkX graph
|
2879
|
+
vertices = []
|
2880
|
+
for node, data in nxGraph.nodes(data=True):
|
2881
|
+
# Attempt to get X, Y, Z from the node data
|
2882
|
+
x = round(data.get(xKey, random.uniform(*range)), mantissa)
|
2883
|
+
y = round(data.get(yKey, random.uniform(*range)), mantissa)
|
2884
|
+
z = round(data.get(zKey, 0), mantissa) # If there are no Z values, this is probably a flat graph.
|
2885
|
+
# Create a TopologicPy vertex with the node data dictionary
|
2886
|
+
vertex = Vertex.ByCoordinates(x,y,z)
|
2887
|
+
cleaned_values = []
|
2888
|
+
for value in data.values():
|
2889
|
+
if isinstance(value, np.ndarray):
|
2890
|
+
value = list(value)
|
2891
|
+
cleaned_values.append(value)
|
2892
|
+
|
2893
|
+
node_dict = Dictionary.ByKeysValues(list(data.keys()), cleaned_values)
|
2894
|
+
vertex = Topology.SetDictionary(vertex, node_dict)
|
2895
|
+
nx_to_topologic_vertex[node] = vertex
|
2896
|
+
vertices.append(vertex)
|
2897
|
+
|
2898
|
+
# Create TopologicPy edges for each edge in the NetworkX graph
|
2899
|
+
edges = []
|
2900
|
+
for u, v, data in nxGraph.edges(data=True):
|
2901
|
+
start_vertex = nx_to_topologic_vertex[u]
|
2902
|
+
end_vertex = nx_to_topologic_vertex[v]
|
2903
|
+
|
2904
|
+
# Create a TopologicPy edge with the edge data dictionary
|
2905
|
+
edge_dict = Dictionary.ByKeysValues(list(data.keys()), list(data.values()))
|
2906
|
+
edge = Edge.ByVertices([start_vertex, end_vertex], tolerance=tolerance)
|
2907
|
+
edge = Topology.SetDictionary(edge, edge_dict)
|
2908
|
+
edges.append(edge)
|
2909
|
+
|
2910
|
+
# Create and return the TopologicPy graph
|
2911
|
+
topologic_graph = Graph.ByVerticesEdges(vertices, edges)
|
2912
|
+
return topologic_graph
|
2913
|
+
|
2712
2914
|
@staticmethod
|
2713
2915
|
def ByTopology(topology,
|
2714
2916
|
direct: bool = True,
|
@@ -4081,6 +4283,121 @@ class Graph:
|
|
4081
4283
|
v = Topology.SetDictionary(v, d)
|
4082
4284
|
return graph
|
4083
4285
|
|
4286
|
+
@staticmethod
|
4287
|
+
def Complete(graph, silent: bool = False):
|
4288
|
+
"""
|
4289
|
+
Completes the graph by conneting unconnected vertices.
|
4290
|
+
|
4291
|
+
Parameters
|
4292
|
+
----------
|
4293
|
+
graph : topologic_core.Graph
|
4294
|
+
The input graph.
|
4295
|
+
tolerance : float , optional
|
4296
|
+
The desired tolerance. The default is 0.0001.
|
4297
|
+
silent : bool , optional
|
4298
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
4299
|
+
|
4300
|
+
Returns
|
4301
|
+
-------
|
4302
|
+
topologicpy.Graph
|
4303
|
+
the completed graph
|
4304
|
+
"""
|
4305
|
+
from topologicpy.Edge import Edge
|
4306
|
+
from topologicpy.Topology import Topology
|
4307
|
+
|
4308
|
+
if not Topology.IsInstance(graph, "Graph"):
|
4309
|
+
if not silent:
|
4310
|
+
print("Graph.ConnectedComponents - Error: The input graph is not a valid graph. Returning None.")
|
4311
|
+
return None
|
4312
|
+
|
4313
|
+
vertices = Graph.Vertices(graph)
|
4314
|
+
edges = Graph.Edges(graph)
|
4315
|
+
visited = set()
|
4316
|
+
new_edges = []
|
4317
|
+
for sv in vertices:
|
4318
|
+
for ev in vertices:
|
4319
|
+
if sv != ev and not (sv, ev) in visited:
|
4320
|
+
visited.add((sv, ev))
|
4321
|
+
visited.add((ev,sv))
|
4322
|
+
edge = Graph.Edge(graph, sv, ev)
|
4323
|
+
if edge == None:
|
4324
|
+
new_edges.append(Edge.ByVertices(sv, ev))
|
4325
|
+
edges += new_edges
|
4326
|
+
return Graph.ByVerticesEdges(vertices, edges)
|
4327
|
+
|
4328
|
+
@staticmethod
|
4329
|
+
def ConnectedComponents(graph, tolerance: float = 0.0001, silent: bool = False):
|
4330
|
+
"""
|
4331
|
+
Returns the connected components (islands) of the input graph.
|
4332
|
+
|
4333
|
+
Parameters
|
4334
|
+
----------
|
4335
|
+
graph : topologic_core.Graph
|
4336
|
+
The input graph.
|
4337
|
+
tolerance : float , optional
|
4338
|
+
The desired tolerance. The default is 0.0001.
|
4339
|
+
silent : bool , optional
|
4340
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
4341
|
+
|
4342
|
+
Returns
|
4343
|
+
-------
|
4344
|
+
list
|
4345
|
+
The list of connected components (island graphs).
|
4346
|
+
The list is sorted by the number of vertices in each component (from highest to lowest).
|
4347
|
+
|
4348
|
+
"""
|
4349
|
+
def find_connected_components(adjacency_dict):
|
4350
|
+
visited = set()
|
4351
|
+
components = []
|
4352
|
+
|
4353
|
+
for vertex_id in adjacency_dict:
|
4354
|
+
if vertex_id not in visited:
|
4355
|
+
# Perform DFS using a stack
|
4356
|
+
stack = [vertex_id]
|
4357
|
+
current_island = set()
|
4358
|
+
|
4359
|
+
while stack:
|
4360
|
+
current = stack.pop()
|
4361
|
+
if current not in visited:
|
4362
|
+
visited.add(current)
|
4363
|
+
current_island.add(current)
|
4364
|
+
stack.extend(set(adjacency_dict[current]) - visited)
|
4365
|
+
|
4366
|
+
components.append(current_island)
|
4367
|
+
|
4368
|
+
return components
|
4369
|
+
|
4370
|
+
from topologicpy.Topology import Topology
|
4371
|
+
from topologicpy.Dictionary import Dictionary
|
4372
|
+
from topologicpy.Helper import Helper
|
4373
|
+
|
4374
|
+
if not Topology.IsInstance(graph, "Graph"):
|
4375
|
+
if not silent:
|
4376
|
+
print("Graph.ConnectedComponents - Error: The input graph is not a valid graph. Returning None.")
|
4377
|
+
return None
|
4378
|
+
|
4379
|
+
labelKey = "__label__"
|
4380
|
+
lengths = [] #List of lengths to sort the list of components by number of their vertices
|
4381
|
+
vertices = Graph.Vertices(graph)
|
4382
|
+
g_dict = Graph.AdjacencyDictionary(graph, vertexLabelKey=labelKey)
|
4383
|
+
components = find_connected_components(g_dict)
|
4384
|
+
return_components = []
|
4385
|
+
for component in components:
|
4386
|
+
i_verts = []
|
4387
|
+
for v in component:
|
4388
|
+
vert = Topology.Filter(vertices, searchType="equal to", key=labelKey, value=v)['filtered'][0]
|
4389
|
+
d = Topology.Dictionary(vert)
|
4390
|
+
d = Dictionary.RemoveKey(d, labelKey)
|
4391
|
+
vert = Topology.SetDictionary(vert, d)
|
4392
|
+
i_verts.append(vert)
|
4393
|
+
i_edges = Graph.Edges(graph, i_verts)
|
4394
|
+
lengths.append(len(i_verts))
|
4395
|
+
g_component = Graph.ByVerticesEdges(i_verts, i_edges)
|
4396
|
+
return_components.append(g_component)
|
4397
|
+
return_components = Helper.Sort(return_components, lengths)
|
4398
|
+
return_components.reverse()
|
4399
|
+
return return_components
|
4400
|
+
|
4084
4401
|
@staticmethod
|
4085
4402
|
def ContractEdge(graph, edge, vertex=None, tolerance=0.0001):
|
4086
4403
|
"""
|
@@ -4184,7 +4501,7 @@ class Graph:
|
|
4184
4501
|
return graph
|
4185
4502
|
|
4186
4503
|
@staticmethod
|
4187
|
-
def ClosenessCentrality(graph, vertices=None, key: str = "closeness_centrality", mantissa: int = 6, tolerance = 0.0001):
|
4504
|
+
def ClosenessCentrality(graph, vertices=None, key: str = "closeness_centrality", mantissa: int = 6, tolerance = 0.0001, silent = False):
|
4188
4505
|
"""
|
4189
4506
|
Return the closeness 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 closeness centrality of all the vertices in the input graph is computed. See https://en.wikipedia.org/wiki/Closeness_centrality.
|
4190
4507
|
|
@@ -4200,6 +4517,8 @@ class Graph:
|
|
4200
4517
|
The desired length of the mantissa. The default is 6.
|
4201
4518
|
tolerance : float , optional
|
4202
4519
|
The desired tolerance. The default is 0.0001.
|
4520
|
+
silent : bool , optional
|
4521
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
4203
4522
|
|
4204
4523
|
Returns
|
4205
4524
|
-------
|
@@ -4207,56 +4526,130 @@ class Graph:
|
|
4207
4526
|
The closeness centrality of the input list of vertices within the input graph. The values are in the range 0 to 1.
|
4208
4527
|
|
4209
4528
|
"""
|
4529
|
+
|
4530
|
+
def closeness_centrality(g):
|
4531
|
+
"""
|
4532
|
+
Computes the closeness centrality for each vertex in the graph.
|
4533
|
+
|
4534
|
+
Parameters:
|
4535
|
+
graph (dict): A dictionary representing the graph where keys are vertices and
|
4536
|
+
values are lists of neighboring vertices.
|
4537
|
+
|
4538
|
+
Returns:
|
4539
|
+
dict: A dictionary where keys are vertices and values are their closeness centrality.
|
4540
|
+
"""
|
4541
|
+
keys = list(g.keys())
|
4542
|
+
N = len(keys)
|
4543
|
+
|
4544
|
+
centralities = []
|
4545
|
+
for v in keys:
|
4546
|
+
total_distance = 0
|
4547
|
+
reachable_count = 0
|
4548
|
+
|
4549
|
+
for u in keys:
|
4550
|
+
if v != u:
|
4551
|
+
distance = Graph._topological_distance(g, v, u)
|
4552
|
+
if distance != None:
|
4553
|
+
total_distance += distance
|
4554
|
+
reachable_count += 1
|
4555
|
+
|
4556
|
+
if reachable_count > 0: # Avoid division by zero
|
4557
|
+
centrality = (reachable_count / total_distance)
|
4558
|
+
else:
|
4559
|
+
centrality = 0.0 # Isolated vertex
|
4560
|
+
|
4561
|
+
centralities.append(centrality)
|
4562
|
+
return centralities
|
4563
|
+
|
4564
|
+
from topologicpy.Vertex import Vertex
|
4210
4565
|
from topologicpy.Topology import Topology
|
4211
4566
|
from topologicpy.Dictionary import Dictionary
|
4567
|
+
from topologicpy.Helper import Helper
|
4212
4568
|
|
4213
4569
|
if not Topology.IsInstance(graph, "Graph"):
|
4214
|
-
|
4570
|
+
if not silent:
|
4571
|
+
print("Graph.ClosenessCentrality - Error: The input graph is not a valid graph. Returning None.")
|
4215
4572
|
return None
|
4573
|
+
g = Graph.AdjacencyDictionary(graph)
|
4574
|
+
centralities = closeness_centrality(g)
|
4216
4575
|
graphVertices = Graph.Vertices(graph)
|
4217
|
-
if
|
4218
|
-
|
4576
|
+
if vertices == None:
|
4577
|
+
for i, v in enumerate(graphVertices):
|
4578
|
+
d = Topology.Dictionary(v)
|
4579
|
+
d = Dictionary.SetValueAtKey(d, key, centralities[i])
|
4580
|
+
v = Topology.SetDictionary(v, d)
|
4581
|
+
return centralities
|
4219
4582
|
else:
|
4220
|
-
|
4221
|
-
|
4222
|
-
|
4223
|
-
|
4224
|
-
|
4583
|
+
return_centralities = []
|
4584
|
+
for v in vertices:
|
4585
|
+
i = Vertex.Index(v, graphVertices)
|
4586
|
+
d = Topology.Dictionary(v)
|
4587
|
+
d = Dictionary.SetValueAtKey(d, key, centralities[i])
|
4588
|
+
v = Topology.SetDictionary(v, d)
|
4589
|
+
return_centralities.append(centralities[i])
|
4590
|
+
return centralities
|
4225
4591
|
|
4226
|
-
|
4592
|
+
@staticmethod
|
4593
|
+
def Community(graph, key: str = "community", mantissa: int = 6, tolerance: float = 0.0001, silent: bool = False):
|
4594
|
+
"""
|
4595
|
+
Computes the best community partition of the input graph based on the Louvain method. See https://en.wikipedia.org/wiki/Louvain_method.
|
4596
|
+
This method depends on NetworkX and the python-louvain libraries
|
4597
|
+
|
4598
|
+
Parameters
|
4599
|
+
----------
|
4600
|
+
graph : topologicp.Graph
|
4601
|
+
The input topologic graph.
|
4602
|
+
key : str , optional
|
4603
|
+
The dictionary key under which to save the closeness centrality score. The default is "community".
|
4604
|
+
mantissa : int , optional
|
4605
|
+
The desired length of the mantissa. The default is 6.
|
4606
|
+
tolerance : float , optional
|
4607
|
+
The desired tolerance. The default is 0.0001.
|
4608
|
+
silent : bool , optional
|
4609
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
4610
|
+
Returns
|
4611
|
+
-------
|
4612
|
+
topologicpy.Graph
|
4613
|
+
The created topologic graph.
|
4614
|
+
|
4615
|
+
"""
|
4616
|
+
from topologicpy.Topology import Topology
|
4617
|
+
from topologicpy.Dictionary import Dictionary
|
4618
|
+
import os
|
4619
|
+
import warnings
|
4620
|
+
|
4227
4621
|
try:
|
4228
|
-
|
4229
|
-
top_dist = 0
|
4230
|
-
for vb in graphVertices:
|
4231
|
-
if Topology.IsSame(va, vb):
|
4232
|
-
d = 0
|
4233
|
-
else:
|
4234
|
-
d = Graph.TopologicalDistance(graph, va, vb, tolerance=tolerance)
|
4235
|
-
top_dist += d
|
4236
|
-
if top_dist == 0:
|
4237
|
-
print("Graph.ClosenessCentrality - Warning: Topological Distance is Zero.")
|
4238
|
-
scores.append(0)
|
4239
|
-
else:
|
4240
|
-
scores.append(round((n-1)/top_dist, mantissa))
|
4622
|
+
import community as community_louvain
|
4241
4623
|
except:
|
4242
|
-
print("Graph.
|
4243
|
-
|
4244
|
-
|
4245
|
-
|
4246
|
-
|
4247
|
-
|
4248
|
-
|
4249
|
-
|
4250
|
-
|
4251
|
-
|
4252
|
-
|
4253
|
-
|
4254
|
-
|
4255
|
-
|
4256
|
-
|
4257
|
-
|
4258
|
-
|
4259
|
-
|
4624
|
+
print("Graph.Community - Installing required pyhon-louvain library.")
|
4625
|
+
try:
|
4626
|
+
os.system("pip install python-louvain")
|
4627
|
+
except:
|
4628
|
+
os.system("pip install python-louvain --user")
|
4629
|
+
try:
|
4630
|
+
import community as community_louvain
|
4631
|
+
print("Graph.Community - python-louvain library installed correctly.")
|
4632
|
+
except:
|
4633
|
+
warnings.warn("Graph.Community - Error: Could not import python-louvain. Please install manually.")
|
4634
|
+
|
4635
|
+
if not Topology.IsInstance(graph, "graph"):
|
4636
|
+
if not silent:
|
4637
|
+
print("Graph.Community - Error: The input graph parameter is not a valid topologic graph. Returning None")
|
4638
|
+
return None
|
4639
|
+
|
4640
|
+
vertices = Graph.Vertices(graph)
|
4641
|
+
nx_graph = Graph.NetworkXGraph(graph, mantissa=mantissa, tolerance=tolerance)
|
4642
|
+
# Apply the Louvain algorithm
|
4643
|
+
partition = community_louvain.best_partition(nx_graph)
|
4644
|
+
communities = []
|
4645
|
+
# Add the partition value to each node's properties
|
4646
|
+
for node, community_id in partition.items():
|
4647
|
+
nx_graph.nodes[node][key] = community_id
|
4648
|
+
d = Topology.Dictionary(vertices[node])
|
4649
|
+
d = Dictionary.SetValueAtKey(d, key, community_id)
|
4650
|
+
vertices[node] = Topology.SetDictionary(vertices[node], d)
|
4651
|
+
communities.append(community_id)
|
4652
|
+
return communities
|
4260
4653
|
|
4261
4654
|
@staticmethod
|
4262
4655
|
def Connect(graph, verticesA, verticesB, tolerance=0.0001):
|
@@ -4305,6 +4698,53 @@ class Graph:
|
|
4305
4698
|
_ = graph.Connect(verticesA, verticesB, tolerance) # Hook to Core
|
4306
4699
|
return graph
|
4307
4700
|
|
4701
|
+
@staticmethod
|
4702
|
+
def Connectivity(graph, vertices=None, key: str = "connectivity", edgeKey: str = None, tolerance = 0.0001, silent = False):
|
4703
|
+
"""
|
4704
|
+
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/.
|
4705
|
+
|
4706
|
+
Parameters
|
4707
|
+
----------
|
4708
|
+
graph : topologic_core.Graph
|
4709
|
+
The input graph.
|
4710
|
+
vertices : list , optional
|
4711
|
+
The input list of vertices. The default is None.
|
4712
|
+
key : str , optional
|
4713
|
+
The dictionary key under which to save the connectivity score. The default is "connectivity".
|
4714
|
+
edgeKey : str , optional
|
4715
|
+
If specified, the value in the connected edges' dictionary specified by the edgeKey string will be aggregated to calculate
|
4716
|
+
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.
|
4717
|
+
tolerance : float , optional
|
4718
|
+
The desired tolerance. The default is 0.0001.
|
4719
|
+
|
4720
|
+
tolerance : float , optional
|
4721
|
+
The desired tolerance. The default is 0.0001.
|
4722
|
+
silent : bool , optional
|
4723
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
4724
|
+
|
4725
|
+
Returns
|
4726
|
+
-------
|
4727
|
+
list
|
4728
|
+
The closeness centrality of the input list of vertices within the input graph. The values are in the range 0 to 1.
|
4729
|
+
|
4730
|
+
"""
|
4731
|
+
|
4732
|
+
from topologicpy.Topology import Topology
|
4733
|
+
from topologicpy.Dictionary import Dictionary
|
4734
|
+
|
4735
|
+
if not Topology.IsInstance(graph, "Graph"):
|
4736
|
+
if not silent:
|
4737
|
+
print("Graph.ClosenessCentrality - Error: The input graph is not a valid graph. Returning None.")
|
4738
|
+
return None
|
4739
|
+
if vertices == None:
|
4740
|
+
vertices = Graph.Vertices(graph)
|
4741
|
+
connectivities = [Graph.VertexDegree(graph, v, edgeKey=edgeKey, tolerance=tolerance, silent=silent) for v in vertices]
|
4742
|
+
for i, v in enumerate(vertices):
|
4743
|
+
d = Topology.Dictionary(v)
|
4744
|
+
d = Dictionary.SetValueAtKey(d, key, connectivities[i])
|
4745
|
+
v = Topology.SetDictionary(v, d)
|
4746
|
+
return connectivities
|
4747
|
+
|
4308
4748
|
@staticmethod
|
4309
4749
|
def ContainsEdge(graph, edge, tolerance=0.0001):
|
4310
4750
|
"""
|
@@ -5790,11 +6230,12 @@ class Graph:
|
|
5790
6230
|
|
5791
6231
|
@staticmethod
|
5792
6232
|
def Reshape(graph,
|
5793
|
-
shape="
|
6233
|
+
shape="spring 2D",
|
5794
6234
|
k=0.8, seed=None,
|
5795
6235
|
iterations=50,
|
5796
6236
|
rootVertex=None,
|
5797
6237
|
size=1,
|
6238
|
+
factor=1,
|
5798
6239
|
sides=16,
|
5799
6240
|
key="",
|
5800
6241
|
tolerance=0.0001,
|
@@ -5808,16 +6249,17 @@ class Graph:
|
|
5808
6249
|
The input graph.
|
5809
6250
|
shape : str , optional
|
5810
6251
|
The desired shape of the graph.
|
5811
|
-
|
5812
|
-
If set to '
|
5813
|
-
If set to '
|
5814
|
-
If set to '
|
5815
|
-
If set to '
|
5816
|
-
If set to '
|
5817
|
-
If set to '
|
5818
|
-
If set to '
|
5819
|
-
If set to '
|
5820
|
-
|
6252
|
+
['circle 2D', 'grid 2D', 'line 2D', 'radial 2D', 'spring 2D', 'tree 2D', 'grid 3D', 'sphere 3D', 'tree 3D']
|
6253
|
+
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.
|
6254
|
+
If set to 'radial 2D', the nodes will be distributed along concentric circles in the XY plane.
|
6255
|
+
If set to 'tree 2D' or 'tree 3D', the nodes will be distributed using the Reingold-Tillford layout.
|
6256
|
+
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).
|
6257
|
+
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).
|
6258
|
+
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).
|
6259
|
+
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).
|
6260
|
+
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).
|
6261
|
+
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)
|
6262
|
+
The default is 'spring 2D'.
|
5821
6263
|
k : float, optional
|
5822
6264
|
The desired spring constant to use for the attractive and repulsive forces. The default is 0.8.
|
5823
6265
|
seed : int , optional
|
@@ -5826,6 +6268,8 @@ class Graph:
|
|
5826
6268
|
The desired maximum number of iterations to solve the forces in the 'spring' mode. The default is 50.
|
5827
6269
|
rootVertex : topologic_core.Vertex , optional
|
5828
6270
|
The desired vertex to use as the root of the tree and radial layouts.
|
6271
|
+
size : float , optional
|
6272
|
+
The desired overall size of the graph.
|
5829
6273
|
sides : int , optional
|
5830
6274
|
The desired number of sides of the circle layout option. The default is 16
|
5831
6275
|
length : float, optional
|
@@ -6055,7 +6499,7 @@ class Graph:
|
|
6055
6499
|
|
6056
6500
|
for i, c_v in enumerate(c_vertices):
|
6057
6501
|
d = Topology.Dictionary(vertices[i])
|
6058
|
-
c_v = Topology.SetDictionary(c_v, d)
|
6502
|
+
c_v = Topology.SetDictionary(c_v, d, silent=True)
|
6059
6503
|
adj_dict = Graph.AdjacencyDictionary(graph)
|
6060
6504
|
keys = adj_dict.keys()
|
6061
6505
|
|
@@ -6077,7 +6521,7 @@ class Graph:
|
|
6077
6521
|
orig_edge_index = edge_dict.get(str(x)+"_"+str(y), edge_dict.get(str(y)+"_"+str(x), None))
|
6078
6522
|
if orig_edge_index:
|
6079
6523
|
d = Topology.Dictionary(edges[orig_edge_index])
|
6080
|
-
e = Topology.SetDictionary(e, d)
|
6524
|
+
e = Topology.SetDictionary(e, d, silent=True)
|
6081
6525
|
c_edges.append(e)
|
6082
6526
|
used[x][y] = 1
|
6083
6527
|
used[y][x] = 1
|
@@ -6247,7 +6691,7 @@ class Graph:
|
|
6247
6691
|
c_vertices = [Vertex.ByCoordinates(coord) for coord in c_points]
|
6248
6692
|
for i, c_v in enumerate(c_vertices):
|
6249
6693
|
d = Topology.Dictionary(vertices[i])
|
6250
|
-
c_v = Topology.SetDictionary(c_v, d)
|
6694
|
+
c_v = Topology.SetDictionary(c_v, d, silent=True)
|
6251
6695
|
adj_dict = Graph.AdjacencyDictionary(graph)
|
6252
6696
|
keys = adj_dict.keys()
|
6253
6697
|
|
@@ -6269,7 +6713,7 @@ class Graph:
|
|
6269
6713
|
orig_edge_index = edge_dict.get(str(x)+"_"+str(y), edge_dict.get(str(y)+"_"+str(x), None))
|
6270
6714
|
if orig_edge_index:
|
6271
6715
|
d = Topology.Dictionary(edges[orig_edge_index])
|
6272
|
-
e = Topology.SetDictionary(e, d)
|
6716
|
+
e = Topology.SetDictionary(e, d, silent=True)
|
6273
6717
|
c_edges.append(e)
|
6274
6718
|
used[x][y] = 1
|
6275
6719
|
used[y][x] = 1
|
@@ -6299,7 +6743,7 @@ class Graph:
|
|
6299
6743
|
|
6300
6744
|
for i, c_v in enumerate(c_vertices):
|
6301
6745
|
d = Topology.Dictionary(vertices[i])
|
6302
|
-
c_v = Topology.SetDictionary(c_v, d)
|
6746
|
+
c_v = Topology.SetDictionary(c_v, d, silent=True)
|
6303
6747
|
adj_dict = Graph.AdjacencyDictionary(graph)
|
6304
6748
|
keys = adj_dict.keys()
|
6305
6749
|
|
@@ -6314,7 +6758,7 @@ class Graph:
|
|
6314
6758
|
orig_edge_index = edge_dict.get(str(x)+"_"+str(y), edge_dict.get(str(y)+"_"+str(x), None))
|
6315
6759
|
if orig_edge_index:
|
6316
6760
|
d = Topology.Dictionary(edges[orig_edge_index])
|
6317
|
-
e = Topology.SetDictionary(e, d)
|
6761
|
+
e = Topology.SetDictionary(e, d, silent=True)
|
6318
6762
|
c_edges.append(e)
|
6319
6763
|
used[x][y] = 1
|
6320
6764
|
used[y][x] = 1
|
@@ -6340,7 +6784,7 @@ class Graph:
|
|
6340
6784
|
|
6341
6785
|
for i, c_v in enumerate(c_vertices):
|
6342
6786
|
d = Topology.Dictionary(vertices[i])
|
6343
|
-
c_v = Topology.SetDictionary(c_v, d)
|
6787
|
+
c_v = Topology.SetDictionary(c_v, d, silent=True)
|
6344
6788
|
adj_dict = Graph.AdjacencyDictionary(graph)
|
6345
6789
|
keys = adj_dict.keys()
|
6346
6790
|
c_edges = []
|
@@ -6357,7 +6801,7 @@ class Graph:
|
|
6357
6801
|
|
6358
6802
|
orig_edge_index = edge_dict[str(x)+"_"+str(y)]
|
6359
6803
|
d = Topology.Dictionary(edges[orig_edge_index])
|
6360
|
-
e = Topology.SetDictionary(e, d)
|
6804
|
+
e = Topology.SetDictionary(e, d, silent=True)
|
6361
6805
|
c_edges.append(e)
|
6362
6806
|
used[x][y] = 1
|
6363
6807
|
used[y][x] = 1
|
@@ -6382,7 +6826,7 @@ class Graph:
|
|
6382
6826
|
|
6383
6827
|
for i, c_v in enumerate(c_vertices):
|
6384
6828
|
d = Topology.Dictionary(vertices[i])
|
6385
|
-
c_v = Topology.SetDictionary(c_v, d)
|
6829
|
+
c_v = Topology.SetDictionary(c_v, d, silent=True)
|
6386
6830
|
adj_dict = Graph.AdjacencyDictionary(graph)
|
6387
6831
|
keys = adj_dict.keys()
|
6388
6832
|
|
@@ -6397,7 +6841,7 @@ class Graph:
|
|
6397
6841
|
orig_edge_index = edge_dict.get(str(x)+"_"+str(y), edge_dict.get(str(y)+"_"+str(x), None))
|
6398
6842
|
if orig_edge_index:
|
6399
6843
|
d = Topology.Dictionary(edges[orig_edge_index])
|
6400
|
-
e = Topology.SetDictionary(e, d)
|
6844
|
+
e = Topology.SetDictionary(e, d, silent=True)
|
6401
6845
|
c_edges.append(e)
|
6402
6846
|
used[x][y] = 1
|
6403
6847
|
used[y][x] = 1
|
@@ -6574,46 +7018,97 @@ class Graph:
|
|
6574
7018
|
|
6575
7019
|
return pos
|
6576
7020
|
|
7021
|
+
|
7022
|
+
|
6577
7023
|
def radial_layout_2d(edge_list, root_index=0):
|
7024
|
+
import numpy as np
|
7025
|
+
from collections import deque
|
7026
|
+
# Build tree and get layout from Buchheim
|
6578
7027
|
root, num_nodes = tree_from_edge_list(edge_list, root_index)
|
6579
7028
|
dt = buchheim(root)
|
6580
|
-
pos = np.zeros((num_nodes, 2))
|
6581
|
-
|
6582
|
-
pos[int(dt.tree.node), 0] = dt.x
|
6583
|
-
pos[int(dt.tree.node), 1] = dt.y
|
6584
|
-
|
6585
|
-
old_roots = [dt]
|
6586
|
-
new_roots = []
|
6587
7029
|
|
6588
|
-
|
6589
|
-
|
6590
|
-
|
6591
|
-
children = temp_root.children
|
6592
|
-
for child in children:
|
6593
|
-
pos[int(child.tree.node), 0] = child.x
|
6594
|
-
pos[int(child.tree.node), 1] = child.y
|
6595
|
-
new_roots.extend(children)
|
6596
|
-
|
6597
|
-
old_roots = new_roots
|
6598
|
-
|
6599
|
-
# pos[:, 1] = np.max(pos[:, 1]) - pos[:, 1]
|
6600
|
-
pos[:, 0] = pos[:, 0] - np.min(pos[:, 0])
|
6601
|
-
pos[:, 1] = pos[:, 1] - np.min(pos[:, 1])
|
6602
|
-
|
6603
|
-
pos[:, 0] = pos[:, 0] / np.max(pos[:, 0])
|
6604
|
-
pos[:, 0] = pos[:, 0] - pos[:, 0][root_index]
|
6605
|
-
|
6606
|
-
range_ = np.max(pos[:, 0]) - np.min(pos[:, 0])
|
6607
|
-
pos[:, 0] = pos[:, 0] / range_
|
6608
|
-
|
6609
|
-
pos[:, 0] = pos[:, 0] * np.pi * 1.98
|
6610
|
-
pos[:, 1] = pos[:, 1] / np.max(pos[:, 1])
|
7030
|
+
# Initialize positions array
|
7031
|
+
pos = np.zeros((num_nodes, 2))
|
7032
|
+
pos[int(dt.tree.node)] = [dt.x, dt.y]
|
6611
7033
|
|
6612
|
-
|
7034
|
+
# Efficient tree traversal using a queue
|
7035
|
+
queue = deque([dt])
|
7036
|
+
while queue:
|
7037
|
+
current = queue.popleft()
|
7038
|
+
for child in current.children:
|
7039
|
+
pos[int(child.tree.node)] = [child.x, child.y]
|
7040
|
+
queue.append(child)
|
7041
|
+
|
7042
|
+
# Normalize positions
|
7043
|
+
pos[:, 0] -= np.min(pos[:, 0])
|
7044
|
+
pos[:, 1] -= np.min(pos[:, 1])
|
7045
|
+
pos[:, 0] /= np.max(pos[:, 0])
|
7046
|
+
pos[:, 1] /= np.max(pos[:, 1])
|
7047
|
+
|
7048
|
+
# Center the root and scale the x-coordinates
|
7049
|
+
pos[:, 0] -= pos[root_index, 0]
|
7050
|
+
pos[:, 0] /= (np.max(pos[:, 0]) - np.min(pos[:, 0]))
|
7051
|
+
pos[:, 0] *= np.pi * 1.98
|
7052
|
+
|
7053
|
+
# Convert to polar coordinates
|
7054
|
+
new_pos = np.zeros_like(pos)
|
6613
7055
|
new_pos[:, 0] = pos[:, 1] * np.cos(pos[:, 0])
|
6614
7056
|
new_pos[:, 1] = pos[:, 1] * np.sin(pos[:, 0])
|
6615
|
-
|
7057
|
+
|
6616
7058
|
return new_pos
|
7059
|
+
|
7060
|
+
def dendrimer_layout_2d(graph, root_index=0, base_radius=1, radius_factor=1.5):
|
7061
|
+
"""
|
7062
|
+
Given a graph as an adjacency dictionary, this function generates a dendrimer layout
|
7063
|
+
and returns positions of the nodes in 2D space.
|
7064
|
+
|
7065
|
+
:param graph: dict, adjacency dictionary where keys are node ids and values are sets/lists of neighboring nodes
|
7066
|
+
:param root_index: int, index of the node to start the layout (default: 0)
|
7067
|
+
:return: list of positions [x, y] for each node, sorted by node id
|
7068
|
+
"""
|
7069
|
+
import numpy as np
|
7070
|
+
|
7071
|
+
# Initialize variables
|
7072
|
+
positions = {}
|
7073
|
+
visited = set()
|
7074
|
+
layers = {}
|
7075
|
+
|
7076
|
+
# Helper function to perform a DFS and organize nodes in layers
|
7077
|
+
def dfs(node, depth):
|
7078
|
+
visited.add(node)
|
7079
|
+
if depth not in layers:
|
7080
|
+
layers[depth] = []
|
7081
|
+
layers[depth].append(node)
|
7082
|
+
for neighbor in graph.get(node, []):
|
7083
|
+
if neighbor not in visited:
|
7084
|
+
dfs(neighbor, depth + 1)
|
7085
|
+
|
7086
|
+
# Start DFS from the given root node
|
7087
|
+
starting_node = list(graph.keys())[root_index]
|
7088
|
+
dfs(starting_node, 0)
|
7089
|
+
|
7090
|
+
# Perform DFS for all nodes to handle disconnected components
|
7091
|
+
for node in graph.keys():
|
7092
|
+
if node not in visited:
|
7093
|
+
dfs(node, 0) # Start a new DFS for each unvisited node
|
7094
|
+
|
7095
|
+
# Compute positions based on layers
|
7096
|
+
for depth, nodes in layers.items():
|
7097
|
+
print("depth:", depth)
|
7098
|
+
# Place nodes in a circular arrangement at each layer
|
7099
|
+
num_nodes = len(nodes)
|
7100
|
+
angle_step = 2 * np.pi / num_nodes if num_nodes > 0 else 0
|
7101
|
+
for i, node in enumerate(nodes):
|
7102
|
+
angle = i * angle_step
|
7103
|
+
x = base_radius*depth*np.cos(angle)
|
7104
|
+
y = base_radius*depth*np.sin(angle)
|
7105
|
+
positions[node] = (x, y)
|
7106
|
+
|
7107
|
+
# Sort the positions by node id and return them
|
7108
|
+
keys = list(positions.keys())
|
7109
|
+
keys.sort()
|
7110
|
+
return_values = [list(positions[key]) for key in keys]
|
7111
|
+
return return_values
|
6617
7112
|
|
6618
7113
|
def spherical_layout_3d(edge_list, root_index=0):
|
6619
7114
|
root, num_nodes = tree_from_edge_list(edge_list, root_index)
|
@@ -6686,7 +7181,7 @@ class Graph:
|
|
6686
7181
|
return line_layout_2d(graph, length=size)
|
6687
7182
|
elif 'grid' in shape.lower() and '2d' in shape.lower():
|
6688
7183
|
return grid_layout_2d(graph, size=size)
|
6689
|
-
elif 'sphere'
|
7184
|
+
elif 'sphere' in shape.lower() and '3d' in shape.lower():
|
6690
7185
|
return sphere_layout_3d(graph, radius=size/2)
|
6691
7186
|
elif 'grid' in shape.lower() and '3d' in shape.lower():
|
6692
7187
|
return grid_layout_3d(graph, size=size)
|
@@ -6717,12 +7212,13 @@ class Graph:
|
|
6717
7212
|
elif 'tree' in shape.lower() and '2d' in shape.lower():
|
6718
7213
|
positions = tree_layout_2d(edges, root_index=root_index)
|
6719
7214
|
elif 'tree' in shape.lower() and '3d' in shape.lower():
|
6720
|
-
positions = tree_layout_3d(edges, root_index=root_index, base_radius=
|
7215
|
+
positions = tree_layout_3d(edges, root_index=root_index, base_radius=size/2, radius_factor=factor)
|
7216
|
+
elif 'dendrimer' in shape.lower() and '2d' in shape.lower():
|
7217
|
+
positions = dendrimer_layout_2d(Graph.AdjacencyDictionary(graph), root_index=root_index, base_radius=size/2, radius_factor=factor)
|
6721
7218
|
else:
|
6722
7219
|
if not silent:
|
6723
7220
|
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.")
|
6724
7221
|
return None
|
6725
|
-
positions = positions.tolist()
|
6726
7222
|
if len(positions[0]) == 3:
|
6727
7223
|
positions = [[p[0], p[1], p[2]] for p in positions]
|
6728
7224
|
else:
|
@@ -7253,6 +7749,113 @@ class Graph:
|
|
7253
7749
|
json_string = json.dumps(json_data, indent=indent, sort_keys=sortKeys)
|
7254
7750
|
return json_string
|
7255
7751
|
|
7752
|
+
@staticmethod
|
7753
|
+
def Leaves(graph, edgeKey: str = None, tolerance: float = 0.0001, silent: bool = False):
|
7754
|
+
"""
|
7755
|
+
Returns a list of all vertices that have a degree of 1, also called leaf nodes.
|
7756
|
+
|
7757
|
+
Parameters
|
7758
|
+
----------
|
7759
|
+
graph : topologic_core.Graph
|
7760
|
+
The input graph.
|
7761
|
+
edgeKey : str , optional
|
7762
|
+
If specified, the value in the connected edges' dictionary specified by the edgeKey string will be aggregated to calculate
|
7763
|
+
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.
|
7764
|
+
tolerance : float , optional
|
7765
|
+
The desired tolerance. The default is 0.0001.
|
7766
|
+
silent : bool , optional
|
7767
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
7768
|
+
|
7769
|
+
Returns
|
7770
|
+
-------
|
7771
|
+
list
|
7772
|
+
The list of leaf nodes
|
7773
|
+
|
7774
|
+
"""
|
7775
|
+
from topologicpy.Topology import Topology
|
7776
|
+
|
7777
|
+
if not Topology.IsInstance(graph, "graph"):
|
7778
|
+
if not silent:
|
7779
|
+
print("Graph.Leaves - Error: The input graph parameter is not a valid graph. Returning None.")
|
7780
|
+
return None
|
7781
|
+
return [v for v in Graph.Vertices(graph) if Graph.VertexDegree(graph, v, edgeKey=edgeKey, tolerance=tolerance, silent=silent) == 1]
|
7782
|
+
|
7783
|
+
@staticmethod
|
7784
|
+
def LineGraph(graph, transferVertexDictionaries=False, transferEdgeDictionaries=False, tolerance=0.0001, silent=False):
|
7785
|
+
"""
|
7786
|
+
Create a line graph based on the input graph. See https://en.wikipedia.org/wiki/Line_graph.
|
7787
|
+
|
7788
|
+
Parameters
|
7789
|
+
----------
|
7790
|
+
graph : topologic_core.Graph
|
7791
|
+
The input graph.
|
7792
|
+
transferVertexDictionaries : bool, optional
|
7793
|
+
If set to True, the dictionaries of the vertices of the input graph are transferred to the edges of the line graph.
|
7794
|
+
transferEdgeDictionaries : bool, optional
|
7795
|
+
If set to True, the dictionaries of the edges of the input graph are transferred to the vertices of the line graph.
|
7796
|
+
tolerance : float, optional
|
7797
|
+
The desired tolerance. The default is 0.0001.
|
7798
|
+
silent : bool , optional
|
7799
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
7800
|
+
|
7801
|
+
Returns
|
7802
|
+
-------
|
7803
|
+
topologic_core.Graph
|
7804
|
+
The created line graph.
|
7805
|
+
|
7806
|
+
"""
|
7807
|
+
from topologicpy.Edge import Edge
|
7808
|
+
from topologicpy.Topology import Topology
|
7809
|
+
|
7810
|
+
if not Topology.IsInstance(graph, "graph"):
|
7811
|
+
if not silent:
|
7812
|
+
print("Graph.LineGraph - Error: The input graph parameter is not a valid graph. Returning None.")
|
7813
|
+
return None
|
7814
|
+
|
7815
|
+
graph_vertices = Graph.Vertices(graph)
|
7816
|
+
graph_edges = Graph.Edges(graph)
|
7817
|
+
|
7818
|
+
# Create line graph vertices (centroids of original graph edges)
|
7819
|
+
if transferEdgeDictionaries == True:
|
7820
|
+
lg_vertices = [
|
7821
|
+
Topology.SetDictionary(Topology.Centroid(edge), Topology.Dictionary(edge), silent=silent)
|
7822
|
+
for edge in graph_edges
|
7823
|
+
]
|
7824
|
+
else:
|
7825
|
+
lg_vertices = [Topology.Centroid(edge) for edge in graph_edges]
|
7826
|
+
|
7827
|
+
lg_edges = []
|
7828
|
+
if transferVertexDictionaries == True:
|
7829
|
+
for v in graph_vertices:
|
7830
|
+
edges = Graph.Edges(graph, vertices=[v])
|
7831
|
+
if len(edges) > 1:
|
7832
|
+
d = Topology.Dictionary(v) # Only need to call Dictionary once
|
7833
|
+
visited = set() # Use a set to track visited pairs of edges
|
7834
|
+
centroids = [Topology.Centroid(e) for e in edges] # Precompute centroids once
|
7835
|
+
for i in range(len(edges)):
|
7836
|
+
for j in range(i + 1, len(edges)): # Only loop over pairs (i, j) where i < j
|
7837
|
+
if (i, j) not in visited:
|
7838
|
+
lg_edge = Edge.ByVertices([centroids[i], centroids[j]], tolerance=tolerance, silent=silent)
|
7839
|
+
lg_edge = Topology.SetDictionary(lg_edge, d, silent=silent)
|
7840
|
+
lg_edges.append(lg_edge)
|
7841
|
+
visited.add((i, j))
|
7842
|
+
visited.add((j, i)) # Ensure both directions are marked as visited
|
7843
|
+
else:
|
7844
|
+
for v in graph_vertices:
|
7845
|
+
edges = Graph.Edges(graph, vertices=[v])
|
7846
|
+
if len(edges) > 1:
|
7847
|
+
visited = set() # Use a set to track visited pairs of edges
|
7848
|
+
centroids = [Topology.Centroid(e) for e in edges] # Precompute centroids once
|
7849
|
+
for i in range(len(edges)):
|
7850
|
+
for j in range(i + 1, len(edges)): # Only loop over pairs (i, j) where i < j
|
7851
|
+
if (i, j) not in visited:
|
7852
|
+
lg_edge = Edge.ByVertices([centroids[i], centroids[j]], tolerance=tolerance, silent=silent)
|
7853
|
+
lg_edges.append(lg_edge)
|
7854
|
+
visited.add((i, j))
|
7855
|
+
visited.add((j, i)) # Ensure both directions are marked as visited
|
7856
|
+
|
7857
|
+
return Graph.ByVerticesEdges(lg_vertices, lg_edges)
|
7858
|
+
|
7256
7859
|
@staticmethod
|
7257
7860
|
def LocalClusteringCoefficient(graph, vertices: list = None, key: str = "lcc", mantissa: int = 6, tolerance: float = 0.0001):
|
7258
7861
|
"""
|
@@ -7912,18 +8515,26 @@ class Graph:
|
|
7912
8515
|
return nearestVertex
|
7913
8516
|
|
7914
8517
|
@staticmethod
|
7915
|
-
def NetworkXGraph(graph, mantissa: int = 6, tolerance: float = 0.0001):
|
8518
|
+
def NetworkXGraph(graph, xKey='x', yKey='y', zKey='z', mantissa: int = 6, tolerance: float = 0.0001, silent: bool = False):
|
7916
8519
|
"""
|
7917
|
-
|
8520
|
+
Converts the input graph into a NetworkX Graph. See http://networkx.org
|
7918
8521
|
|
7919
8522
|
Parameters
|
7920
8523
|
----------
|
7921
8524
|
graph : topologic_core.Graph
|
7922
8525
|
The input graph.
|
8526
|
+
xKey : str , optional
|
8527
|
+
The dictionary key under which to save the X-Coordinate of the vertex. The default is 'x'.
|
8528
|
+
yKey : str , optional
|
8529
|
+
The dictionary key under which to save the Y-Coordinate of the vertex. The default is 'y'.
|
8530
|
+
zKey : str , optional
|
8531
|
+
The dictionary key under which to save the Z-Coordinate of the vertex. The default is 'z'.
|
7923
8532
|
mantissa : int , optional
|
7924
8533
|
The desired length of the mantissa. The default is 6.
|
7925
8534
|
tolerance : float , optional
|
7926
8535
|
The desired tolerance. The default is 0.0001.
|
8536
|
+
silent : bool , optional
|
8537
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
7927
8538
|
|
7928
8539
|
Returns
|
7929
8540
|
-------
|
@@ -7951,7 +8562,8 @@ class Graph:
|
|
7951
8562
|
return None
|
7952
8563
|
|
7953
8564
|
if not Topology.IsInstance(graph, "Graph"):
|
7954
|
-
|
8565
|
+
if not silent:
|
8566
|
+
print("Graph.NetworkXGraph - Error: The input graph is not a valid graph. Returning None.")
|
7955
8567
|
return None
|
7956
8568
|
|
7957
8569
|
nxGraph = nx.Graph()
|
@@ -7968,8 +8580,7 @@ class Graph:
|
|
7968
8580
|
values = Dictionary.Values(d)
|
7969
8581
|
if not values:
|
7970
8582
|
values = []
|
7971
|
-
keys += [
|
7972
|
-
import random
|
8583
|
+
keys += [xKey,yKey,zKey]
|
7973
8584
|
values += [Vertex.X(v, mantissa=mantissa), Vertex.Y(v, mantissa=mantissa), Vertex.Z(v, mantissa=mantissa)]
|
7974
8585
|
d = Dictionary.ByKeysValues(keys,values)
|
7975
8586
|
pythonD = Dictionary.PythonDictionary(d)
|
@@ -8879,6 +9490,28 @@ class Graph:
|
|
8879
9490
|
return None
|
8880
9491
|
return len(Graph.Edges(graph))
|
8881
9492
|
|
9493
|
+
@staticmethod
|
9494
|
+
def _topological_distance(g, start, target):
|
9495
|
+
from collections import deque
|
9496
|
+
if start == target:
|
9497
|
+
return 0
|
9498
|
+
visited = set()
|
9499
|
+
queue = deque([(start, 0)]) # Each element is a tuple (vertex, distance)
|
9500
|
+
|
9501
|
+
while queue:
|
9502
|
+
current, distance = queue.popleft()
|
9503
|
+
if current in visited:
|
9504
|
+
continue
|
9505
|
+
|
9506
|
+
visited.add(current)
|
9507
|
+
for neighbor in g.get(current, []):
|
9508
|
+
if neighbor == target:
|
9509
|
+
return distance + 1
|
9510
|
+
if neighbor not in visited:
|
9511
|
+
queue.append((neighbor, distance + 1))
|
9512
|
+
|
9513
|
+
return None # Target not reachable
|
9514
|
+
|
8882
9515
|
@staticmethod
|
8883
9516
|
def TopologicalDistance(graph, vertexA, vertexB, tolerance=0.0001):
|
8884
9517
|
"""
|
@@ -8901,6 +9534,8 @@ class Graph:
|
|
8901
9534
|
The topological distance between the input vertices.
|
8902
9535
|
|
8903
9536
|
"""
|
9537
|
+
|
9538
|
+
from topologicpy.Vertex import Vertex
|
8904
9539
|
from topologicpy.Topology import Topology
|
8905
9540
|
|
8906
9541
|
if not Topology.IsInstance(graph, "Graph"):
|
@@ -8912,7 +9547,19 @@ class Graph:
|
|
8912
9547
|
if not Topology.IsInstance(vertexB, "Vertex"):
|
8913
9548
|
print("Graph.TopologicalDistance - Error: The input vertexB is not a valid vertex. Returning None.")
|
8914
9549
|
return None
|
8915
|
-
|
9550
|
+
|
9551
|
+
g = Graph.AdjacencyDictionary(graph)
|
9552
|
+
vertices = Graph.Vertices(graph)
|
9553
|
+
keys = list(g.keys())
|
9554
|
+
index_a = Vertex.Index(vertexA, vertices, tolerance=tolerance)
|
9555
|
+
if index_a == None:
|
9556
|
+
return 0
|
9557
|
+
start = keys[index_a]
|
9558
|
+
index_b = Vertex.Index(vertexB, vertices, tolerance=tolerance)
|
9559
|
+
if index_b == None:
|
9560
|
+
return 0
|
9561
|
+
target = keys[index_b]
|
9562
|
+
return Graph._topological_distance(g, start, target)
|
8916
9563
|
|
8917
9564
|
@staticmethod
|
8918
9565
|
def Topology(graph):
|
@@ -9016,7 +9663,7 @@ class Graph:
|
|
9016
9663
|
return Graph.ByVerticesEdges(dictionary['vertices'], dictionary['edges'])
|
9017
9664
|
|
9018
9665
|
@staticmethod
|
9019
|
-
def VertexDegree(graph, vertex, edgeKey: str = None, tolerance: float = 0.0001):
|
9666
|
+
def VertexDegree(graph, vertex, edgeKey: str = None, tolerance: float = 0.0001, silent: bool = False):
|
9020
9667
|
"""
|
9021
9668
|
Returns the degree of the input vertex. See https://en.wikipedia.org/wiki/Degree_(graph_theory).
|
9022
9669
|
|
@@ -9031,6 +9678,8 @@ class Graph:
|
|
9031
9678
|
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.
|
9032
9679
|
tolerance : float , optional
|
9033
9680
|
The desired tolerance. The default is 0.0001.
|
9681
|
+
silent : bool , optional
|
9682
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
9034
9683
|
|
9035
9684
|
Returns
|
9036
9685
|
-------
|
@@ -9043,10 +9692,12 @@ class Graph:
|
|
9043
9692
|
import numbers
|
9044
9693
|
|
9045
9694
|
if not Topology.IsInstance(graph, "Graph"):
|
9046
|
-
|
9695
|
+
if not silent:
|
9696
|
+
print("Graph.VertexDegree - Error: The input graph is not a valid graph. Returning None.")
|
9047
9697
|
return None
|
9048
9698
|
if not Topology.IsInstance(vertex, "Vertex"):
|
9049
|
-
|
9699
|
+
if not silent:
|
9700
|
+
print("Graph.VertexDegree - Error: The input vertex is not a valid vertex. Returning None.")
|
9050
9701
|
return None
|
9051
9702
|
if not isinstance(edgeKey, str):
|
9052
9703
|
edgeKey = ""
|