topologicpy 0.7.90__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/Face.py +128 -6
- topologicpy/Graph.py +573 -66
- topologicpy/Plotly.py +43 -22
- topologicpy/version.py +1 -1
- {topologicpy-0.7.90.dist-info → topologicpy-0.7.91.dist-info}/METADATA +1 -1
- {topologicpy-0.7.90.dist-info → topologicpy-0.7.91.dist-info}/RECORD +9 -9
- {topologicpy-0.7.90.dist-info → topologicpy-0.7.91.dist-info}/LICENSE +0 -0
- {topologicpy-0.7.90.dist-info → topologicpy-0.7.91.dist-info}/WHEEL +0 -0
- {topologicpy-0.7.90.dist-info → topologicpy-0.7.91.dist-info}/top_level.txt +0 -0
topologicpy/Face.py
CHANGED
@@ -1690,6 +1690,8 @@ class Face():
|
|
1690
1690
|
"""
|
1691
1691
|
Returns the face representing the isovist projection from the input viewpoint.
|
1692
1692
|
This method assumes all input is in 2D. Z coordinates are ignored.
|
1693
|
+
This method and the metrics are largely derived from isovists.org. Even if not explicitly listed, please assume that all credit
|
1694
|
+
goes to the authors of that website and its associated software.
|
1693
1695
|
|
1694
1696
|
Parameters
|
1695
1697
|
----------
|
@@ -1731,6 +1733,12 @@ class Face():
|
|
1731
1733
|
- d_f : float, Fractal Dimension measures the complexity of the isovist's boundary.
|
1732
1734
|
- e_c : float , Edge Complexity measures how complex the edges of the isovist boundary are.
|
1733
1735
|
- theta : float, Mean Visual Field Angle measures the average angular extent of the visible area from the observation point.
|
1736
|
+
- occlusivity: float, the proportion of edges of an isovist that are not physically defined.
|
1737
|
+
- drift: float, the distance from the observation point to the centroid of its isovist.
|
1738
|
+
- closed_perimeter: float, the total length of non-occluded edges of the isovist.
|
1739
|
+
- average_radial: float, "the mean view length of all space visible from a location." (from isovists.org)
|
1740
|
+
- variance: float, "the mean of the square of deviation between all radial lengths and average radial length of an isovist (Benedikt, 1979)." (from isovists.org)
|
1741
|
+
- skewness: float, "the mean of the cube of deviation between all radial lengths and average radial length of an isovist (Benediky, 1979)." (from isovists.org)
|
1734
1742
|
triangles : bool , optional
|
1735
1743
|
If set to True, the subtended triangles of the isovist are created and stored as contents of the returned isovist face. The default is False.
|
1736
1744
|
mantissa : int , optional
|
@@ -1934,6 +1942,81 @@ class Face():
|
|
1934
1942
|
|
1935
1943
|
return float(distance), Vertex.ByCoordinates(list(closest_point))
|
1936
1944
|
|
1945
|
+
|
1946
|
+
|
1947
|
+
def compute_average_radial_variance_skewness(vertex, edges, mantissa=6):
|
1948
|
+
from math import atan2, pi, sqrt, pow
|
1949
|
+
|
1950
|
+
def subtended_angle(vertex, edge, mantissa=6):
|
1951
|
+
"""Compute the angle subtended by the edge at point V."""
|
1952
|
+
v = Vertex.Coordinates(vertex, mantissa=mantissa)
|
1953
|
+
start = Vertex.Coordinates(Edge.StartVertex(edge), mantissa=mantissa)
|
1954
|
+
end = Vertex.Coordinates(Edge.EndVertex(edge), mantissa=mantissa)
|
1955
|
+
# Calculate the angles of the start and end vertices relative to V
|
1956
|
+
angle_start = atan2(start[1] - v[1], start[0] - v[0])
|
1957
|
+
angle_end = atan2(end[1] - v[1], end[0] - v[0])
|
1958
|
+
# Ensure the angle is in the range [0, 2*pi]
|
1959
|
+
angle_start = angle_start if angle_start >= 0 else angle_start + 2 * pi
|
1960
|
+
angle_end = angle_end if angle_end >= 0 else angle_end + 2 * pi
|
1961
|
+
# Compute the difference and handle wrapping around 2*pi
|
1962
|
+
angle_diff = abs(angle_end - angle_start)
|
1963
|
+
return min(angle_diff, 2 * pi - angle_diff)
|
1964
|
+
|
1965
|
+
total_weighted_distance = 0
|
1966
|
+
total_angle_weight = 0
|
1967
|
+
total_weighted_squared_deviation = 0
|
1968
|
+
total_weighted_cubed_deviation = 0
|
1969
|
+
distances = []
|
1970
|
+
angles = []
|
1971
|
+
for edge in edges:
|
1972
|
+
# Calculate the distance between V and the edge
|
1973
|
+
distance = Vertex.Distance(vertex, edge, mantissa=mantissa)
|
1974
|
+
distances.append(distance)
|
1975
|
+
|
1976
|
+
# Calculate the subtended angle for the edge
|
1977
|
+
angle = subtended_angle(vertex, edge, mantissa=mantissa)
|
1978
|
+
angles.append(angle)
|
1979
|
+
|
1980
|
+
# Weight the distance by the subtended angle
|
1981
|
+
total_weighted_distance += distance * angle
|
1982
|
+
total_angle_weight += angle
|
1983
|
+
|
1984
|
+
# Compute the Average Radial value
|
1985
|
+
if total_angle_weight == 0:
|
1986
|
+
average_radial = 0 # Avoid division by zero
|
1987
|
+
else:
|
1988
|
+
average_radial = round(total_weighted_distance / total_angle_weight, mantissa)
|
1989
|
+
|
1990
|
+
# Compute Variance
|
1991
|
+
for i, edge in enumerate(edges):
|
1992
|
+
# Calculate the distance between V and the edge
|
1993
|
+
distance = distances[i]
|
1994
|
+
# Calculate the subtended angle for the edge
|
1995
|
+
angle = angles[i]
|
1996
|
+
|
1997
|
+
# Calculate the deviation squared from the average radial
|
1998
|
+
deviation_squared = (distance - average_radial) ** 2
|
1999
|
+
# Calculate the deviation cubed from the average radial
|
2000
|
+
deviation_cubed = (distance - average_radial) ** 3
|
2001
|
+
# Weight the squared deviation by the subtended angle
|
2002
|
+
total_weighted_squared_deviation += deviation_squared * angle
|
2003
|
+
total_weighted_cubed_deviation += deviation_cubed * angle
|
2004
|
+
|
2005
|
+
# Compute the Variance value
|
2006
|
+
if total_angle_weight == 0:
|
2007
|
+
variance = 0 # Avoid division by zero
|
2008
|
+
else:
|
2009
|
+
variance = round(sqrt(total_weighted_squared_deviation / total_angle_weight), mantissa)
|
2010
|
+
|
2011
|
+
# Compute the Skewness value
|
2012
|
+
if total_angle_weight == 0:
|
2013
|
+
skewness = 0 # Avoid division by zero
|
2014
|
+
else:
|
2015
|
+
skewness = round(pow(total_weighted_cubed_deviation / total_angle_weight, 1/3), mantissa)
|
2016
|
+
|
2017
|
+
return average_radial, variance, skewness
|
2018
|
+
|
2019
|
+
# Main Code
|
1937
2020
|
origin = Topology.Centroid(face)
|
1938
2021
|
normal = Face.Normal(face)
|
1939
2022
|
flat_face = Topology.Flatten(face, origin=origin, direction=normal)
|
@@ -2022,7 +2105,7 @@ class Face():
|
|
2022
2105
|
return None
|
2023
2106
|
simpler_face = Face.RemoveCollinearEdges(return_face)
|
2024
2107
|
if Topology.IsInstance(simpler_face, "face"):
|
2025
|
-
if transferDictionaries == True:
|
2108
|
+
if transferDictionaries == True or metrics == True:
|
2026
2109
|
j_edges = [Topology.Edges(t) for t in obstacles]
|
2027
2110
|
j_edges = Helper.Flatten(j_edges)
|
2028
2111
|
j_edges += Topology.Edges(face)
|
@@ -2030,19 +2113,24 @@ class Face():
|
|
2030
2113
|
used = [0 for _ in range(len(j_edges))]
|
2031
2114
|
for i, i_edge in enumerate(i_edges):
|
2032
2115
|
d_i = Topology.Dictionary(i_edge)
|
2116
|
+
d_i = Dictionary.SetValueAtKey(d_i, "occlusive", True)
|
2117
|
+
i_edge = Topology.SetDictionary(i_edge, d_i)
|
2033
2118
|
for j, j_edge in enumerate(j_edges):
|
2034
2119
|
if used[j] == 0:
|
2035
2120
|
if Edge.IsCollinear(i_edge, j_edge):
|
2121
|
+
d_i = Dictionary.SetValueAtKey(d_i, "occlusive", False)
|
2122
|
+
i_edge = Topology.SetDictionary(i_edge, d_i)
|
2036
2123
|
d_j = Topology.Dictionary(j_edge)
|
2037
2124
|
d_result = Dictionary.ByMergedDictionaries([d_i, d_j])
|
2038
|
-
|
2125
|
+
if transferDictionaries == True:
|
2126
|
+
i_edge = Topology.SetDictionary(i_edge, d_result)
|
2039
2127
|
used[j] == 1
|
2040
2128
|
|
2041
2129
|
return_face = Topology.Unflatten(simpler_face, origin=origin, direction=normal)
|
2042
|
-
|
2130
|
+
else:
|
2131
|
+
return_face = Topology.Unflatten(return_face, origin=origin, direction=normal)
|
2043
2132
|
if metrics == True:
|
2044
2133
|
vertices = Topology.Vertices(return_face)
|
2045
|
-
edges = Topology.Edges(return_face)
|
2046
2134
|
# 1 Viewpoint
|
2047
2135
|
viewpoint = Vertex.Coordinates(vertex, mantissa=mantissa)
|
2048
2136
|
# 2 Direction
|
@@ -2110,6 +2198,27 @@ class Face():
|
|
2110
2198
|
e_c = round(edge_complexity(coords), mantissa)
|
2111
2199
|
# 18 Mean Visual Field Angle
|
2112
2200
|
theta = round(mean_visual_field_angle(viewpoint, coords), mantissa)
|
2201
|
+
# 19 Occlusivity
|
2202
|
+
occ_length = 0
|
2203
|
+
edges = Topology.Edges(return_face)
|
2204
|
+
for edge in edges:
|
2205
|
+
d = Topology.Dictionary(edge)
|
2206
|
+
if Dictionary.ValueAtKey(d, "occlusive") == True:
|
2207
|
+
occ_length += Edge.Length(edge)
|
2208
|
+
if perimeter > 0:
|
2209
|
+
occlusivity = round(occ_length/perimeter, mantissa)
|
2210
|
+
else:
|
2211
|
+
occlusivity = round(0.0, 6)
|
2212
|
+
|
2213
|
+
# 20 Drift
|
2214
|
+
drift = Vertex.Distance(vertex, Topology.Centroid(return_face), mantissa=mantissa)
|
2215
|
+
|
2216
|
+
# 21 Closed Perimeter
|
2217
|
+
closed_perimeter = round(perimeter - occ_length, mantissa)
|
2218
|
+
|
2219
|
+
# 22/23/24 Average Radial, Variance, and Skewness
|
2220
|
+
average_radial, variance, skewness = compute_average_radial_variance_skewness(vertex, edges, mantissa=6)
|
2221
|
+
|
2113
2222
|
keys = ["viewpoint",
|
2114
2223
|
"direction",
|
2115
2224
|
"fov",
|
@@ -2127,7 +2236,14 @@ class Face():
|
|
2127
2236
|
"symmetry",
|
2128
2237
|
"d_f",
|
2129
2238
|
"e_c",
|
2130
|
-
"theta"
|
2239
|
+
"theta",
|
2240
|
+
"occlusivity",
|
2241
|
+
"drift",
|
2242
|
+
"closed_perimeter",
|
2243
|
+
"average_radial",
|
2244
|
+
"variance",
|
2245
|
+
"skewness"]
|
2246
|
+
|
2131
2247
|
values = [viewpoint,
|
2132
2248
|
direction,
|
2133
2249
|
fov,
|
@@ -2145,7 +2261,13 @@ class Face():
|
|
2145
2261
|
symmetry,
|
2146
2262
|
d_f,
|
2147
2263
|
e_c,
|
2148
|
-
theta
|
2264
|
+
theta,
|
2265
|
+
occlusivity,
|
2266
|
+
drift,
|
2267
|
+
closed_perimeter,
|
2268
|
+
average_radial,
|
2269
|
+
variance,
|
2270
|
+
skewness]
|
2149
2271
|
d = Dictionary.ByKeysValues(keys, values)
|
2150
2272
|
return_face = Topology.SetDictionary(return_face, d)
|
2151
2273
|
if triangles:
|
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
|
|
@@ -2729,7 +2835,82 @@ class Graph:
|
|
2729
2835
|
g_e = Topology.SetDictionary(g_e, d)
|
2730
2836
|
g_edges.append(g_e)
|
2731
2837
|
return Graph.ByVerticesEdges(g_vertices, g_edges)
|
2732
|
-
|
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
|
+
|
2733
2914
|
@staticmethod
|
2734
2915
|
def ByTopology(topology,
|
2735
2916
|
direct: bool = True,
|
@@ -4102,6 +4283,48 @@ class Graph:
|
|
4102
4283
|
v = Topology.SetDictionary(v, d)
|
4103
4284
|
return graph
|
4104
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
|
+
|
4105
4328
|
@staticmethod
|
4106
4329
|
def ConnectedComponents(graph, tolerance: float = 0.0001, silent: bool = False):
|
4107
4330
|
"""
|
@@ -4366,6 +4589,68 @@ class Graph:
|
|
4366
4589
|
return_centralities.append(centralities[i])
|
4367
4590
|
return centralities
|
4368
4591
|
|
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
|
+
|
4621
|
+
try:
|
4622
|
+
import community as community_louvain
|
4623
|
+
except:
|
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
|
4653
|
+
|
4369
4654
|
@staticmethod
|
4370
4655
|
def Connect(graph, verticesA, verticesB, tolerance=0.0001):
|
4371
4656
|
"""
|
@@ -4413,6 +4698,53 @@ class Graph:
|
|
4413
4698
|
_ = graph.Connect(verticesA, verticesB, tolerance) # Hook to Core
|
4414
4699
|
return graph
|
4415
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
|
+
|
4416
4748
|
@staticmethod
|
4417
4749
|
def ContainsEdge(graph, edge, tolerance=0.0001):
|
4418
4750
|
"""
|
@@ -5898,11 +6230,12 @@ class Graph:
|
|
5898
6230
|
|
5899
6231
|
@staticmethod
|
5900
6232
|
def Reshape(graph,
|
5901
|
-
shape="
|
6233
|
+
shape="spring 2D",
|
5902
6234
|
k=0.8, seed=None,
|
5903
6235
|
iterations=50,
|
5904
6236
|
rootVertex=None,
|
5905
6237
|
size=1,
|
6238
|
+
factor=1,
|
5906
6239
|
sides=16,
|
5907
6240
|
key="",
|
5908
6241
|
tolerance=0.0001,
|
@@ -5916,16 +6249,17 @@ class Graph:
|
|
5916
6249
|
The input graph.
|
5917
6250
|
shape : str , optional
|
5918
6251
|
The desired shape of the graph.
|
5919
|
-
|
5920
|
-
If set to '
|
5921
|
-
If set to '
|
5922
|
-
If set to '
|
5923
|
-
If set to '
|
5924
|
-
If set to '
|
5925
|
-
If set to '
|
5926
|
-
If set to '
|
5927
|
-
If set to '
|
5928
|
-
|
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'.
|
5929
6263
|
k : float, optional
|
5930
6264
|
The desired spring constant to use for the attractive and repulsive forces. The default is 0.8.
|
5931
6265
|
seed : int , optional
|
@@ -5934,6 +6268,8 @@ class Graph:
|
|
5934
6268
|
The desired maximum number of iterations to solve the forces in the 'spring' mode. The default is 50.
|
5935
6269
|
rootVertex : topologic_core.Vertex , optional
|
5936
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.
|
5937
6273
|
sides : int , optional
|
5938
6274
|
The desired number of sides of the circle layout option. The default is 16
|
5939
6275
|
length : float, optional
|
@@ -6163,7 +6499,7 @@ class Graph:
|
|
6163
6499
|
|
6164
6500
|
for i, c_v in enumerate(c_vertices):
|
6165
6501
|
d = Topology.Dictionary(vertices[i])
|
6166
|
-
c_v = Topology.SetDictionary(c_v, d)
|
6502
|
+
c_v = Topology.SetDictionary(c_v, d, silent=True)
|
6167
6503
|
adj_dict = Graph.AdjacencyDictionary(graph)
|
6168
6504
|
keys = adj_dict.keys()
|
6169
6505
|
|
@@ -6185,7 +6521,7 @@ class Graph:
|
|
6185
6521
|
orig_edge_index = edge_dict.get(str(x)+"_"+str(y), edge_dict.get(str(y)+"_"+str(x), None))
|
6186
6522
|
if orig_edge_index:
|
6187
6523
|
d = Topology.Dictionary(edges[orig_edge_index])
|
6188
|
-
e = Topology.SetDictionary(e, d)
|
6524
|
+
e = Topology.SetDictionary(e, d, silent=True)
|
6189
6525
|
c_edges.append(e)
|
6190
6526
|
used[x][y] = 1
|
6191
6527
|
used[y][x] = 1
|
@@ -6355,7 +6691,7 @@ class Graph:
|
|
6355
6691
|
c_vertices = [Vertex.ByCoordinates(coord) for coord in c_points]
|
6356
6692
|
for i, c_v in enumerate(c_vertices):
|
6357
6693
|
d = Topology.Dictionary(vertices[i])
|
6358
|
-
c_v = Topology.SetDictionary(c_v, d)
|
6694
|
+
c_v = Topology.SetDictionary(c_v, d, silent=True)
|
6359
6695
|
adj_dict = Graph.AdjacencyDictionary(graph)
|
6360
6696
|
keys = adj_dict.keys()
|
6361
6697
|
|
@@ -6377,7 +6713,7 @@ class Graph:
|
|
6377
6713
|
orig_edge_index = edge_dict.get(str(x)+"_"+str(y), edge_dict.get(str(y)+"_"+str(x), None))
|
6378
6714
|
if orig_edge_index:
|
6379
6715
|
d = Topology.Dictionary(edges[orig_edge_index])
|
6380
|
-
e = Topology.SetDictionary(e, d)
|
6716
|
+
e = Topology.SetDictionary(e, d, silent=True)
|
6381
6717
|
c_edges.append(e)
|
6382
6718
|
used[x][y] = 1
|
6383
6719
|
used[y][x] = 1
|
@@ -6407,7 +6743,7 @@ class Graph:
|
|
6407
6743
|
|
6408
6744
|
for i, c_v in enumerate(c_vertices):
|
6409
6745
|
d = Topology.Dictionary(vertices[i])
|
6410
|
-
c_v = Topology.SetDictionary(c_v, d)
|
6746
|
+
c_v = Topology.SetDictionary(c_v, d, silent=True)
|
6411
6747
|
adj_dict = Graph.AdjacencyDictionary(graph)
|
6412
6748
|
keys = adj_dict.keys()
|
6413
6749
|
|
@@ -6422,7 +6758,7 @@ class Graph:
|
|
6422
6758
|
orig_edge_index = edge_dict.get(str(x)+"_"+str(y), edge_dict.get(str(y)+"_"+str(x), None))
|
6423
6759
|
if orig_edge_index:
|
6424
6760
|
d = Topology.Dictionary(edges[orig_edge_index])
|
6425
|
-
e = Topology.SetDictionary(e, d)
|
6761
|
+
e = Topology.SetDictionary(e, d, silent=True)
|
6426
6762
|
c_edges.append(e)
|
6427
6763
|
used[x][y] = 1
|
6428
6764
|
used[y][x] = 1
|
@@ -6448,7 +6784,7 @@ class Graph:
|
|
6448
6784
|
|
6449
6785
|
for i, c_v in enumerate(c_vertices):
|
6450
6786
|
d = Topology.Dictionary(vertices[i])
|
6451
|
-
c_v = Topology.SetDictionary(c_v, d)
|
6787
|
+
c_v = Topology.SetDictionary(c_v, d, silent=True)
|
6452
6788
|
adj_dict = Graph.AdjacencyDictionary(graph)
|
6453
6789
|
keys = adj_dict.keys()
|
6454
6790
|
c_edges = []
|
@@ -6465,7 +6801,7 @@ class Graph:
|
|
6465
6801
|
|
6466
6802
|
orig_edge_index = edge_dict[str(x)+"_"+str(y)]
|
6467
6803
|
d = Topology.Dictionary(edges[orig_edge_index])
|
6468
|
-
e = Topology.SetDictionary(e, d)
|
6804
|
+
e = Topology.SetDictionary(e, d, silent=True)
|
6469
6805
|
c_edges.append(e)
|
6470
6806
|
used[x][y] = 1
|
6471
6807
|
used[y][x] = 1
|
@@ -6490,7 +6826,7 @@ class Graph:
|
|
6490
6826
|
|
6491
6827
|
for i, c_v in enumerate(c_vertices):
|
6492
6828
|
d = Topology.Dictionary(vertices[i])
|
6493
|
-
c_v = Topology.SetDictionary(c_v, d)
|
6829
|
+
c_v = Topology.SetDictionary(c_v, d, silent=True)
|
6494
6830
|
adj_dict = Graph.AdjacencyDictionary(graph)
|
6495
6831
|
keys = adj_dict.keys()
|
6496
6832
|
|
@@ -6505,7 +6841,7 @@ class Graph:
|
|
6505
6841
|
orig_edge_index = edge_dict.get(str(x)+"_"+str(y), edge_dict.get(str(y)+"_"+str(x), None))
|
6506
6842
|
if orig_edge_index:
|
6507
6843
|
d = Topology.Dictionary(edges[orig_edge_index])
|
6508
|
-
e = Topology.SetDictionary(e, d)
|
6844
|
+
e = Topology.SetDictionary(e, d, silent=True)
|
6509
6845
|
c_edges.append(e)
|
6510
6846
|
used[x][y] = 1
|
6511
6847
|
used[y][x] = 1
|
@@ -6682,46 +7018,97 @@ class Graph:
|
|
6682
7018
|
|
6683
7019
|
return pos
|
6684
7020
|
|
7021
|
+
|
7022
|
+
|
6685
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
|
6686
7027
|
root, num_nodes = tree_from_edge_list(edge_list, root_index)
|
6687
7028
|
dt = buchheim(root)
|
6688
|
-
pos = np.zeros((num_nodes, 2))
|
6689
7029
|
|
6690
|
-
|
6691
|
-
pos
|
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
|
-
|
6717
|
-
pos[:, 0] = pos[:, 0] * np.pi * 1.98
|
6718
|
-
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]
|
6719
7033
|
|
6720
|
-
|
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)
|
6721
7055
|
new_pos[:, 0] = pos[:, 1] * np.cos(pos[:, 0])
|
6722
7056
|
new_pos[:, 1] = pos[:, 1] * np.sin(pos[:, 0])
|
6723
|
-
|
7057
|
+
|
6724
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
|
6725
7112
|
|
6726
7113
|
def spherical_layout_3d(edge_list, root_index=0):
|
6727
7114
|
root, num_nodes = tree_from_edge_list(edge_list, root_index)
|
@@ -6794,7 +7181,7 @@ class Graph:
|
|
6794
7181
|
return line_layout_2d(graph, length=size)
|
6795
7182
|
elif 'grid' in shape.lower() and '2d' in shape.lower():
|
6796
7183
|
return grid_layout_2d(graph, size=size)
|
6797
|
-
elif 'sphere'
|
7184
|
+
elif 'sphere' in shape.lower() and '3d' in shape.lower():
|
6798
7185
|
return sphere_layout_3d(graph, radius=size/2)
|
6799
7186
|
elif 'grid' in shape.lower() and '3d' in shape.lower():
|
6800
7187
|
return grid_layout_3d(graph, size=size)
|
@@ -6825,12 +7212,13 @@ class Graph:
|
|
6825
7212
|
elif 'tree' in shape.lower() and '2d' in shape.lower():
|
6826
7213
|
positions = tree_layout_2d(edges, root_index=root_index)
|
6827
7214
|
elif 'tree' in shape.lower() and '3d' in shape.lower():
|
6828
|
-
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)
|
6829
7218
|
else:
|
6830
7219
|
if not silent:
|
6831
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.")
|
6832
7221
|
return None
|
6833
|
-
positions = positions.tolist()
|
6834
7222
|
if len(positions[0]) == 3:
|
6835
7223
|
positions = [[p[0], p[1], p[2]] for p in positions]
|
6836
7224
|
else:
|
@@ -7361,6 +7749,113 @@ class Graph:
|
|
7361
7749
|
json_string = json.dumps(json_data, indent=indent, sort_keys=sortKeys)
|
7362
7750
|
return json_string
|
7363
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
|
+
|
7364
7859
|
@staticmethod
|
7365
7860
|
def LocalClusteringCoefficient(graph, vertices: list = None, key: str = "lcc", mantissa: int = 6, tolerance: float = 0.0001):
|
7366
7861
|
"""
|
@@ -8020,18 +8515,26 @@ class Graph:
|
|
8020
8515
|
return nearestVertex
|
8021
8516
|
|
8022
8517
|
@staticmethod
|
8023
|
-
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):
|
8024
8519
|
"""
|
8025
|
-
|
8520
|
+
Converts the input graph into a NetworkX Graph. See http://networkx.org
|
8026
8521
|
|
8027
8522
|
Parameters
|
8028
8523
|
----------
|
8029
8524
|
graph : topologic_core.Graph
|
8030
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'.
|
8031
8532
|
mantissa : int , optional
|
8032
8533
|
The desired length of the mantissa. The default is 6.
|
8033
8534
|
tolerance : float , optional
|
8034
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.
|
8035
8538
|
|
8036
8539
|
Returns
|
8037
8540
|
-------
|
@@ -8059,7 +8562,8 @@ class Graph:
|
|
8059
8562
|
return None
|
8060
8563
|
|
8061
8564
|
if not Topology.IsInstance(graph, "Graph"):
|
8062
|
-
|
8565
|
+
if not silent:
|
8566
|
+
print("Graph.NetworkXGraph - Error: The input graph is not a valid graph. Returning None.")
|
8063
8567
|
return None
|
8064
8568
|
|
8065
8569
|
nxGraph = nx.Graph()
|
@@ -8076,8 +8580,7 @@ class Graph:
|
|
8076
8580
|
values = Dictionary.Values(d)
|
8077
8581
|
if not values:
|
8078
8582
|
values = []
|
8079
|
-
keys += [
|
8080
|
-
import random
|
8583
|
+
keys += [xKey,yKey,zKey]
|
8081
8584
|
values += [Vertex.X(v, mantissa=mantissa), Vertex.Y(v, mantissa=mantissa), Vertex.Z(v, mantissa=mantissa)]
|
8082
8585
|
d = Dictionary.ByKeysValues(keys,values)
|
8083
8586
|
pythonD = Dictionary.PythonDictionary(d)
|
@@ -9160,7 +9663,7 @@ class Graph:
|
|
9160
9663
|
return Graph.ByVerticesEdges(dictionary['vertices'], dictionary['edges'])
|
9161
9664
|
|
9162
9665
|
@staticmethod
|
9163
|
-
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):
|
9164
9667
|
"""
|
9165
9668
|
Returns the degree of the input vertex. See https://en.wikipedia.org/wiki/Degree_(graph_theory).
|
9166
9669
|
|
@@ -9175,6 +9678,8 @@ class Graph:
|
|
9175
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.
|
9176
9679
|
tolerance : float , optional
|
9177
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.
|
9178
9683
|
|
9179
9684
|
Returns
|
9180
9685
|
-------
|
@@ -9187,10 +9692,12 @@ class Graph:
|
|
9187
9692
|
import numbers
|
9188
9693
|
|
9189
9694
|
if not Topology.IsInstance(graph, "Graph"):
|
9190
|
-
|
9695
|
+
if not silent:
|
9696
|
+
print("Graph.VertexDegree - Error: The input graph is not a valid graph. Returning None.")
|
9191
9697
|
return None
|
9192
9698
|
if not Topology.IsInstance(vertex, "Vertex"):
|
9193
|
-
|
9699
|
+
if not silent:
|
9700
|
+
print("Graph.VertexDegree - Error: The input vertex is not a valid vertex. Returning None.")
|
9194
9701
|
return None
|
9195
9702
|
if not isinstance(edgeKey, str):
|
9196
9703
|
edgeKey = ""
|
topologicpy/Plotly.py
CHANGED
@@ -411,29 +411,46 @@ class Plotly:
|
|
411
411
|
|
412
412
|
if showVertices:
|
413
413
|
vertices = Graph.Vertices(graph)
|
414
|
-
|
414
|
+
v_dictionaries = [Topology.Dictionary(v) for v in vertices]
|
415
415
|
e_cluster = Cluster.ByTopologies(vertices)
|
416
416
|
geo = Topology.Geometry(e_cluster, mantissa=mantissa)
|
417
417
|
vertices = geo['vertices']
|
418
418
|
if len(vertices) > 0:
|
419
|
-
v_data = Plotly.
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
419
|
+
v_data = Plotly.vertexData(vertices,
|
420
|
+
dictionaries=v_dictionaries,
|
421
|
+
color=vertexColor,
|
422
|
+
colorKey=vertexColorKey,
|
423
|
+
size=vertexSize,
|
424
|
+
sizeKey=vertexSizeKey,
|
425
|
+
labelKey=vertexLabelKey,
|
426
|
+
showVertexLabel=showVertexLabel,
|
427
|
+
groupKey=vertexGroupKey,
|
428
|
+
minGroup=vertexMinGroup,
|
429
|
+
maxGroup=vertexMaxGroup,
|
430
|
+
groups=vertexGroups,
|
431
|
+
legendLabel=vertexLegendLabel,
|
432
|
+
legendGroup=vertexLegendGroup,
|
433
|
+
legendRank=vertexLegendRank,
|
434
|
+
showLegend=showVertexLegend,
|
435
|
+
colorScale=colorScale)
|
436
|
+
|
437
|
+
# v_data = Plotly.DataByTopology(e_cluster,
|
438
|
+
# vertexColor=vertexColor,
|
439
|
+
# vertexColorKey=vertexColorKey,
|
440
|
+
# vertexSize=vertexSize,
|
441
|
+
# vertexSizeKey=vertexSizeKey,
|
442
|
+
# vertexLabelKey=vertexLabelKey,
|
443
|
+
# showVertexLabel=showVertexLabel,
|
444
|
+
# vertexGroupKey=vertexGroupKey,
|
445
|
+
# vertexMinGroup=vertexMinGroup,
|
446
|
+
# vertexMaxGroup=vertexMaxGroup,
|
447
|
+
# vertexGroups=vertexGroups,
|
448
|
+
# showVertexLegend=showVertexLegend,
|
449
|
+
# vertexLegendLabel=vertexLegendLabel,
|
450
|
+
# vertexLegendGroup=vertexLegendGroup,
|
451
|
+
# vertexLegendRank=vertexLegendRank,
|
452
|
+
# colorScale=colorScale)
|
453
|
+
data += [v_data]
|
437
454
|
|
438
455
|
if showEdges:
|
439
456
|
e_dictionaries = []
|
@@ -501,7 +518,7 @@ class Plotly:
|
|
501
518
|
z.append(v[2])
|
502
519
|
colors.append(Color.AnyToHex(color))
|
503
520
|
labels.append("Vertex_"+str(m+1).zfill(n))
|
504
|
-
sizes.append(size)
|
521
|
+
sizes.append(max(size, 1.1))
|
505
522
|
if len(dictionaries) > 0:
|
506
523
|
d = dictionaries[m]
|
507
524
|
if d:
|
@@ -512,7 +529,11 @@ class Plotly:
|
|
512
529
|
if not labelKey == None:
|
513
530
|
labels[m] = str(Dictionary.ValueAtKey(d, key=labelKey)) or labels[m]
|
514
531
|
if not sizeKey == None:
|
515
|
-
sizes[m] = Dictionary.ValueAtKey(d, key=sizeKey)
|
532
|
+
sizes[m] = Dictionary.ValueAtKey(d, key=sizeKey)
|
533
|
+
if sizes[m] == None:
|
534
|
+
sizes[m] = size
|
535
|
+
if sizes[m] <= 0:
|
536
|
+
sizes[m] = 1.1
|
516
537
|
if not groupKey == None:
|
517
538
|
c_value = Dictionary.ValueAtKey(d, key=groupKey)
|
518
539
|
if not c_value == None:
|
@@ -540,7 +561,7 @@ class Plotly:
|
|
540
561
|
if len(labels) < 1:
|
541
562
|
labels = "Vertex_1"
|
542
563
|
if len(sizes) < 1:
|
543
|
-
sizes = size
|
564
|
+
sizes = [size]*len(x)
|
544
565
|
if showVertexLabel == True:
|
545
566
|
mode = "markers+text"
|
546
567
|
else:
|
topologicpy/version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = '0.7.
|
1
|
+
__version__ = '0.7.91'
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: topologicpy
|
3
|
-
Version: 0.7.
|
3
|
+
Version: 0.7.91
|
4
4
|
Summary: An AI-Powered Spatial Modelling and Analysis Software Library for Architecture, Engineering, and Construction.
|
5
5
|
Author-email: Wassim Jabi <wassim.jabi@gmail.com>
|
6
6
|
License: AGPL v3 License
|
@@ -10,14 +10,14 @@ topologicpy/DGL.py,sha256=Dd6O08D-vSxpjHYgKm45JpKiaeGvWlg1BRMzYMAXGNc,138991
|
|
10
10
|
topologicpy/Dictionary.py,sha256=t0O7Du-iPq46FyKqZfcjHfsUK1E8GS_e67R2V5cpkbw,33186
|
11
11
|
topologicpy/Edge.py,sha256=KWOJCkLDwCWyZJ5MKwDhT5umWwCYBHtLOz6ulHrSOfY,67205
|
12
12
|
topologicpy/EnergyModel.py,sha256=AqTtmXE35SxvRXhG3vYAQd7GQDW-6HtjYPHua6ME4Eg,53762
|
13
|
-
topologicpy/Face.py,sha256=
|
14
|
-
topologicpy/Graph.py,sha256=
|
13
|
+
topologicpy/Face.py,sha256=2k1vSRK1M-s588RcqGHEalwIHJ9nT3qgx3pU4ktN0dU,150077
|
14
|
+
topologicpy/Graph.py,sha256=Qm0V17oeIlhr62faoYqJpNUMzAB5d0F-3zes4jCtRi8,460350
|
15
15
|
topologicpy/Grid.py,sha256=2s9cSlWldivn1i9EUz4OOokJyANveqmRe_vR93CAndI,18245
|
16
16
|
topologicpy/Helper.py,sha256=F3h4_qcOD_PHAoVe0tEbEE7_jYyVcaHjtwVs4QHOZuI,23978
|
17
17
|
topologicpy/Honeybee.py,sha256=HfTaEV1R8K1xOVQQy9sBOhBTF_ap8A2RxZOYhirp_Mw,21835
|
18
18
|
topologicpy/Matrix.py,sha256=umgR7An919-wGInXJ1wpqnoQ2jCPdyMe2rcWTZ16upk,8079
|
19
19
|
topologicpy/Neo4j.py,sha256=t52hgE9cVsqkGc7m7fjRsLnyfRHakVHwdvF4ms7ow78,22342
|
20
|
-
topologicpy/Plotly.py,sha256=
|
20
|
+
topologicpy/Plotly.py,sha256=Tvo0_zKVEHtPhsMNNvLy5G0HIys5FPAOyp_o4QN_I_A,115760
|
21
21
|
topologicpy/Polyskel.py,sha256=EFsuh2EwQJGPLiFUjvtXmAwdX-A4r_DxP5hF7Qd3PaU,19829
|
22
22
|
topologicpy/PyG.py,sha256=LU9LCCzjxGPUM31qbaJXZsTvniTtgugxJY7y612t4A4,109757
|
23
23
|
topologicpy/Shell.py,sha256=UdDz3zfIYmGRjoZIseviJ2cXNtR5Kx5tIsZLhWMyO_U,87906
|
@@ -28,9 +28,9 @@ topologicpy/Vector.py,sha256=A1g83zDHep58iVPY8WQ8iHNrSOfGWFEzvVeDuMnjDNY,33078
|
|
28
28
|
topologicpy/Vertex.py,sha256=sYWTbAHqKGRUAJRCIUqrCO_xFhvsXK09Sx7E4dafPLQ,73754
|
29
29
|
topologicpy/Wire.py,sha256=HjagWKoJb8Z3zhgOij_4k6ZnKIl5gk8LletHbsT1ZKU,190632
|
30
30
|
topologicpy/__init__.py,sha256=vlPCanUbxe5NifC4pHcnhSzkmmYcs_UrZrTlVMsxcFs,928
|
31
|
-
topologicpy/version.py,sha256=
|
32
|
-
topologicpy-0.7.
|
33
|
-
topologicpy-0.7.
|
34
|
-
topologicpy-0.7.
|
35
|
-
topologicpy-0.7.
|
36
|
-
topologicpy-0.7.
|
31
|
+
topologicpy/version.py,sha256=em3O4PG8JKL9957EgCcS5zUXOhgyMhAa6bmjZHQ7UmE,23
|
32
|
+
topologicpy-0.7.91.dist-info/LICENSE,sha256=FK0vJ73LuE8PYJAn7LutsReWR47-Ooovw2dnRe5yV6Q,681
|
33
|
+
topologicpy-0.7.91.dist-info/METADATA,sha256=m96FnumZC4Hy2QqaPuRryOZron2mQdjkGJvv5HJs3kM,10513
|
34
|
+
topologicpy-0.7.91.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
35
|
+
topologicpy-0.7.91.dist-info/top_level.txt,sha256=J30bDzW92Ob7hw3zA8V34Jlp-vvsfIkGzkr8sqvb4Uw,12
|
36
|
+
topologicpy-0.7.91.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|