topologicpy 0.8.58__py3-none-any.whl → 0.8.59__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- topologicpy/Graph.py +1280 -621
- topologicpy/Plotly.py +9 -7
- topologicpy/Topology.py +1 -0
- topologicpy/version.py +1 -1
- {topologicpy-0.8.58.dist-info → topologicpy-0.8.59.dist-info}/METADATA +1 -1
- {topologicpy-0.8.58.dist-info → topologicpy-0.8.59.dist-info}/RECORD +9 -9
- {topologicpy-0.8.58.dist-info → topologicpy-0.8.59.dist-info}/WHEEL +0 -0
- {topologicpy-0.8.58.dist-info → topologicpy-0.8.59.dist-info}/licenses/LICENSE +0 -0
- {topologicpy-0.8.58.dist-info → topologicpy-0.8.59.dist-info}/top_level.txt +0 -0
topologicpy/Graph.py
CHANGED
@@ -1901,11 +1901,28 @@ class Graph:
|
|
1901
1901
|
mantissa= mantissa)
|
1902
1902
|
return bot_graph.serialize(format=format)
|
1903
1903
|
|
1904
|
+
|
1904
1905
|
@staticmethod
|
1905
|
-
def BetweennessCentrality(
|
1906
|
+
def BetweennessCentrality(
|
1907
|
+
graph,
|
1908
|
+
method: str = "vertex",
|
1909
|
+
weightKey: str = "length",
|
1910
|
+
normalize: bool = False,
|
1911
|
+
nxCompatible: bool = False,
|
1912
|
+
key: str = "betweenness_centrality",
|
1913
|
+
colorKey: str = "bc_color",
|
1914
|
+
colorScale: str = "viridis",
|
1915
|
+
mantissa: int = 6,
|
1916
|
+
tolerance: float = 0.001,
|
1917
|
+
silent: bool = False
|
1918
|
+
):
|
1906
1919
|
"""
|
1907
|
-
|
1908
|
-
|
1920
|
+
Returns the betweenness centrality of the input graph. The order of the returned list is the same as the order of vertices/edges. See https://en.wikipedia.org/wiki/Betweenness_centrality.
|
1921
|
+
Optimized betweenness centrality (undirected) using Brandes:
|
1922
|
+
- Unweighted: O(VE) BFS per source
|
1923
|
+
- Weighted: Dijkstra-Brandes with binary heap
|
1924
|
+
- Vertex or Edge mode
|
1925
|
+
- Optional NetworkX-compatible normalization or 0..1 rescale
|
1909
1926
|
Parameters
|
1910
1927
|
----------
|
1911
1928
|
graph : topologic_core.Graph
|
@@ -1936,60 +1953,337 @@ class Graph:
|
|
1936
1953
|
-------
|
1937
1954
|
list
|
1938
1955
|
The betweenness centrality of the input list of vertices within the input graph. The values are in the range 0 to 1.
|
1939
|
-
|
1940
1956
|
"""
|
1941
|
-
import
|
1957
|
+
from collections import deque
|
1958
|
+
import math
|
1942
1959
|
|
1943
|
-
|
1944
|
-
import networkx as nx
|
1945
|
-
except:
|
1946
|
-
print("Graph.BetwennessCentrality - Information: Installing required networkx library.")
|
1947
|
-
try:
|
1948
|
-
os.system("pip install networkx")
|
1949
|
-
except:
|
1950
|
-
os.system("pip install networkx --user")
|
1951
|
-
try:
|
1952
|
-
import networkx as nx
|
1953
|
-
print("Graph.BetwennessCentrality - Infromation: networkx library installed correctly.")
|
1954
|
-
except:
|
1955
|
-
warnings.warn("Graph.BetwennessCentrality - Error: Could not import networkx. Please try to install networkx manually. Returning None.")
|
1956
|
-
return None
|
1957
|
-
|
1960
|
+
from topologicpy.Topology import Topology
|
1958
1961
|
from topologicpy.Dictionary import Dictionary
|
1959
1962
|
from topologicpy.Color import Color
|
1960
|
-
from topologicpy.Topology import Topology
|
1961
1963
|
from topologicpy.Helper import Helper
|
1964
|
+
from topologicpy.Vertex import Vertex
|
1965
|
+
from topologicpy.Edge import Edge
|
1966
|
+
# We are inside Graph.* context; Graph.<...> methods available.
|
1967
|
+
|
1968
|
+
# ---------- validate ----------
|
1969
|
+
if not Topology.IsInstance(graph, "graph"):
|
1970
|
+
if not silent:
|
1971
|
+
print("Graph.BetweennessCentrality - Error: The input is not a valid Graph. Returning None.")
|
1972
|
+
return None
|
1973
|
+
|
1974
|
+
vertices = Graph.Vertices(graph)
|
1975
|
+
n = len(vertices)
|
1976
|
+
if n == 0:
|
1977
|
+
if not silent:
|
1978
|
+
print("Graph.BetweennessCentrality - Warning: Graph has no vertices. Returning [].")
|
1979
|
+
return []
|
1980
|
+
|
1981
|
+
method_l = (method or "vertex").lower()
|
1982
|
+
compute_edges = "edge" in method_l
|
1962
1983
|
|
1963
|
-
|
1964
|
-
|
1984
|
+
# ---------- stable vertex indexing ----------
|
1985
|
+
def vkey(v, r=9):
|
1986
|
+
d = Topology.Dictionary(v)
|
1987
|
+
vid = Dictionary.ValueAtKey(d, "id")
|
1988
|
+
if vid is not None:
|
1989
|
+
return ("id", vid)
|
1990
|
+
return ("xyz", round(Vertex.X(v), r), round(Vertex.Y(v), r), round(Vertex.Z(v), r))
|
1991
|
+
|
1992
|
+
idx_of = {vkey(v): i for i, v in enumerate(vertices)}
|
1993
|
+
|
1994
|
+
# ---------- weight handling ----------
|
1995
|
+
dist_attr = None
|
1996
|
+
if isinstance(weightKey, str) and weightKey:
|
1997
|
+
wl = weightKey.lower()
|
1998
|
+
if ("len" in wl) or ("dis" in wl):
|
1965
1999
|
weightKey = "length"
|
1966
|
-
|
1967
|
-
|
1968
|
-
|
1969
|
-
|
1970
|
-
|
1971
|
-
|
1972
|
-
|
1973
|
-
|
1974
|
-
|
1975
|
-
|
1976
|
-
|
1977
|
-
|
2000
|
+
dist_attr = weightKey
|
2001
|
+
|
2002
|
+
def edge_weight(e):
|
2003
|
+
if dist_attr == "length":
|
2004
|
+
try:
|
2005
|
+
return float(Edge.Length(e))
|
2006
|
+
except Exception:
|
2007
|
+
return 1.0
|
2008
|
+
elif dist_attr:
|
2009
|
+
try:
|
2010
|
+
d = Topology.Dictionary(e)
|
2011
|
+
w = Dictionary.ValueAtKey(d, dist_attr)
|
2012
|
+
return float(w) if (w is not None) else 1.0
|
2013
|
+
except Exception:
|
2014
|
+
return 1.0
|
1978
2015
|
else:
|
1979
|
-
|
1980
|
-
|
1981
|
-
|
2016
|
+
return 1.0
|
2017
|
+
|
2018
|
+
# ---------- build undirected adjacency (min weight on multi-edges) ----------
|
2019
|
+
edges = Graph.Edges(graph)
|
2020
|
+
# For per-edge outputs in input order:
|
2021
|
+
edge_end_idx = [] # [(iu, iv)] aligned with edges list (undirected as sorted pair)
|
2022
|
+
tmp_adj = [dict() for _ in range(n)] # temporary: dedup by neighbor with min weight
|
2023
|
+
|
2024
|
+
for e in edges:
|
2025
|
+
try:
|
2026
|
+
u = Edge.StartVertex(e)
|
2027
|
+
v = Edge.EndVertex(e)
|
2028
|
+
except Exception:
|
2029
|
+
continue
|
2030
|
+
iu = idx_of.get(vkey(u))
|
2031
|
+
iv = idx_of.get(vkey(v))
|
2032
|
+
if iu is None or iv is None or iu == iv:
|
2033
|
+
# still store mapping for return list to avoid index error
|
2034
|
+
pair = None
|
2035
|
+
else:
|
2036
|
+
w = edge_weight(e)
|
2037
|
+
# keep minimal weight for duplicates
|
2038
|
+
pu = tmp_adj[iu].get(iv)
|
2039
|
+
if (pu is None) or (w < pu):
|
2040
|
+
tmp_adj[iu][iv] = w
|
2041
|
+
tmp_adj[iv][iu] = w
|
2042
|
+
pair = (iu, iv) if iu < iv else (iv, iu)
|
2043
|
+
edge_end_idx.append(pair)
|
2044
|
+
|
2045
|
+
# finalize adjacency as list-of-tuples for fast loops
|
2046
|
+
adj = [list(neigh.items()) for neigh in tmp_adj] # adj[i] = [(j, w), ...]
|
2047
|
+
del tmp_adj
|
2048
|
+
|
2049
|
+
# detect weightedness
|
2050
|
+
weighted = False
|
2051
|
+
for i in range(n):
|
2052
|
+
if any(abs(w - 1.0) > 1e-12 for _, w in adj[i]):
|
2053
|
+
weighted = True
|
2054
|
+
break
|
2055
|
+
|
2056
|
+
# ---------- Brandes ----------
|
2057
|
+
CB_v = [0.0] * n
|
2058
|
+
CB_e = {} # key: (min_i, max_j) -> score (only if compute_edges)
|
2059
|
+
|
2060
|
+
if n > 1:
|
2061
|
+
if not weighted:
|
2062
|
+
# Unweighted BFS Brandes
|
2063
|
+
for s in range(n):
|
2064
|
+
S = []
|
2065
|
+
P = [[] for _ in range(n)]
|
2066
|
+
sigma = [0.0] * n
|
2067
|
+
sigma[s] = 1.0
|
2068
|
+
dist = [-1] * n
|
2069
|
+
dist[s] = 0
|
2070
|
+
Q = deque([s])
|
2071
|
+
pushQ, popQ = Q.append, Q.popleft
|
2072
|
+
|
2073
|
+
while Q:
|
2074
|
+
v = popQ()
|
2075
|
+
S.append(v)
|
2076
|
+
dv = dist[v]
|
2077
|
+
sv = sigma[v]
|
2078
|
+
for w, _ in adj[v]:
|
2079
|
+
if dist[w] < 0:
|
2080
|
+
dist[w] = dv + 1
|
2081
|
+
pushQ(w)
|
2082
|
+
if dist[w] == dv + 1:
|
2083
|
+
sigma[w] += sv
|
2084
|
+
P[w].append(v)
|
2085
|
+
|
2086
|
+
delta = [0.0] * n
|
2087
|
+
while S:
|
2088
|
+
w = S.pop()
|
2089
|
+
sw = sigma[w]
|
2090
|
+
dw = 1.0 + delta[w]
|
2091
|
+
for v in P[w]:
|
2092
|
+
c = (sigma[v] / sw) * dw
|
2093
|
+
delta[v] += c
|
2094
|
+
if compute_edges:
|
2095
|
+
a, b = (v, w) if v < w else (w, v)
|
2096
|
+
CB_e[a, b] = CB_e.get((a, b), 0.0) + c
|
2097
|
+
if w != s:
|
2098
|
+
CB_v[w] += delta[w]
|
2099
|
+
else:
|
2100
|
+
# Weighted Dijkstra-Brandes
|
2101
|
+
import heapq
|
2102
|
+
EPS = 1e-12
|
2103
|
+
for s in range(n):
|
2104
|
+
S = []
|
2105
|
+
P = [[] for _ in range(n)]
|
2106
|
+
sigma = [0.0] * n
|
2107
|
+
sigma[s] = 1.0
|
2108
|
+
dist = [math.inf] * n
|
2109
|
+
dist[s] = 0.0
|
2110
|
+
H = [(0.0, s)]
|
2111
|
+
pushH, popH = heapq.heappush, heapq.heappop
|
2112
|
+
|
2113
|
+
while H:
|
2114
|
+
dv, v = popH(H)
|
2115
|
+
if dv > dist[v] + EPS:
|
2116
|
+
continue
|
2117
|
+
S.append(v)
|
2118
|
+
sv = sigma[v]
|
2119
|
+
for w, wgt in adj[v]:
|
2120
|
+
nd = dv + wgt
|
2121
|
+
dw = dist[w]
|
2122
|
+
if nd + EPS < dw:
|
2123
|
+
dist[w] = nd
|
2124
|
+
sigma[w] = sv
|
2125
|
+
P[w] = [v]
|
2126
|
+
pushH(H, (nd, w))
|
2127
|
+
elif abs(nd - dw) <= EPS:
|
2128
|
+
sigma[w] += sv
|
2129
|
+
P[w].append(v)
|
2130
|
+
|
2131
|
+
delta = [0.0] * n
|
2132
|
+
while S:
|
2133
|
+
w = S.pop()
|
2134
|
+
sw = sigma[w]
|
2135
|
+
if sw == 0.0:
|
2136
|
+
continue
|
2137
|
+
dw = 1.0 + delta[w]
|
2138
|
+
for v in P[w]:
|
2139
|
+
c = (sigma[v] / sw) * dw
|
2140
|
+
delta[v] += c
|
2141
|
+
if compute_edges:
|
2142
|
+
a, b = (v, w) if v < w else (w, v)
|
2143
|
+
CB_e[a, b] = CB_e.get((a, b), 0.0) + c
|
2144
|
+
if w != s:
|
2145
|
+
CB_v[w] += delta[w]
|
2146
|
+
|
2147
|
+
# ---------- normalization ----------
|
2148
|
+
# NetworkX-compatible normalization (undirected):
|
2149
|
+
# vertices/edges factor = 2/((n-1)(n-2)) for n > 2 when normalized=True
|
2150
|
+
if nxCompatible:
|
2151
|
+
if normalize and n > 2:
|
2152
|
+
scale = 2.0 / ((n - 1) * (n - 2))
|
2153
|
+
CB_v = [v * scale for v in CB_v]
|
2154
|
+
if compute_edges:
|
2155
|
+
for k in list(CB_e.keys()):
|
2156
|
+
CB_e[k] *= scale
|
2157
|
+
# else: leave raw Brandes scores (normalized=False behavior)
|
2158
|
+
values_raw = CB_v if not compute_edges else [
|
2159
|
+
CB_e.get(tuple(sorted(pair)) if pair else None, 0.0) if pair else 0.0
|
2160
|
+
for pair in edge_end_idx
|
2161
|
+
]
|
2162
|
+
values_for_return = values_raw
|
1982
2163
|
else:
|
1983
|
-
|
1984
|
-
|
2164
|
+
# Rescale to [0,1] regardless of theoretical normalization
|
2165
|
+
values_raw = CB_v if not compute_edges else [
|
2166
|
+
CB_e.get(tuple(sorted(pair)) if pair else None, 0.0) if pair else 0.0
|
2167
|
+
for pair in edge_end_idx
|
2168
|
+
]
|
2169
|
+
values_for_return = Helper.Normalize(values_raw)
|
1985
2170
|
|
1986
|
-
|
1987
|
-
|
1988
|
-
|
1989
|
-
d = Dictionary.SetValuesAtKeys(d, [key, colorKey], [value, color])
|
1990
|
-
elements[i] = Topology.SetDictionary(elements[i], d)
|
2171
|
+
# rounding once
|
2172
|
+
if mantissa is not None and mantissa >= 0:
|
2173
|
+
values_for_return = [round(v, mantissa) for v in values_for_return]
|
1991
2174
|
|
1992
|
-
|
2175
|
+
# ---------- color mapping ----------
|
2176
|
+
if values_for_return:
|
2177
|
+
min_v, max_v = min(values_for_return), max(values_for_return)
|
2178
|
+
else:
|
2179
|
+
min_v, max_v = 0.0, 1.0
|
2180
|
+
if abs(max_v - min_v) < tolerance:
|
2181
|
+
max_v = min_v + tolerance
|
2182
|
+
|
2183
|
+
# annotate (vertices or edges) in input order
|
2184
|
+
if compute_edges:
|
2185
|
+
elems = edges
|
2186
|
+
else:
|
2187
|
+
elems = vertices
|
2188
|
+
for i, value in enumerate(values_for_return):
|
2189
|
+
d = Topology.Dictionary(elems[i])
|
2190
|
+
color_hex = Color.AnyToHex(
|
2191
|
+
Color.ByValueInRange(value, minValue=min_v, maxValue=max_v, colorScale=colorScale)
|
2192
|
+
)
|
2193
|
+
d = Dictionary.SetValuesAtKeys(d, [key, colorKey], [value, color_hex])
|
2194
|
+
elems[i] = Topology.SetDictionary(elems[i], d)
|
2195
|
+
|
2196
|
+
return values_for_return
|
2197
|
+
|
2198
|
+
# @staticmethod
|
2199
|
+
# def BetweennessCentrality_old(graph, method: str = "vertex", weightKey="length", normalize: bool = False, nxCompatible: bool = False, key: str = "betweenness_centrality", colorKey="bc_color", colorScale="viridis", mantissa: int = 6, tolerance: float = 0.001, silent: bool = False):
|
2200
|
+
# """
|
2201
|
+
# Returns the betweenness centrality of the input graph. The order of the returned list is the same as the order of vertices/edges. See https://en.wikipedia.org/wiki/Betweenness_centrality.
|
2202
|
+
|
2203
|
+
# Parameters
|
2204
|
+
# ----------
|
2205
|
+
# graph : topologic_core.Graph
|
2206
|
+
# The input graph.
|
2207
|
+
# method : str , optional
|
2208
|
+
# The method of computing the betweenness centrality. The options are "vertex" or "edge". Default is "vertex".
|
2209
|
+
# weightKey : str , optional
|
2210
|
+
# If specified, the value in the connected edges' dictionary specified by the weightKey string will be aggregated to calculate
|
2211
|
+
# the shortest path. If a numeric value cannot be retrieved from an edge, a value of 1 is used instead.
|
2212
|
+
# This is used in weighted graphs. if weightKey is set to "Length" or "Distance", the length of the edge will be used as its weight.
|
2213
|
+
# normalize : bool , optional
|
2214
|
+
# If set to True, the values are normalized to be in the range 0 to 1. Otherwise they are not. Default is False.
|
2215
|
+
# nxCompatible : bool , optional
|
2216
|
+
# If set to True, and normalize input parameter is also set to True, the values are set to be identical to NetworkX values. Otherwise, they are normalized between 0 and 1. Default is False.
|
2217
|
+
# key : str , optional
|
2218
|
+
# The desired dictionary key under which to store the betweenness centrality score. Default is "betweenness_centrality".
|
2219
|
+
# colorKey : str , optional
|
2220
|
+
# The desired dictionary key under which to store the betweenness centrality color. Default is "betweenness_centrality".
|
2221
|
+
# colorScale : str , optional
|
2222
|
+
# The desired type of plotly color scales to use (e.g. "viridis", "plasma"). Default is "viridis". For a full list of names, see https://plotly.com/python/builtin-colorscales/.
|
2223
|
+
# In addition to these, three color-blind friendly scales are included. These are "protanopia", "deuteranopia", and "tritanopia" for red, green, and blue colorblindness respectively.
|
2224
|
+
# mantissa : int , optional
|
2225
|
+
# The number of decimal places to round the result to. Default is 6.
|
2226
|
+
# tolerance : float , optional
|
2227
|
+
# The desired tolerance. Default is 0.0001.
|
2228
|
+
|
2229
|
+
# Returns
|
2230
|
+
# -------
|
2231
|
+
# list
|
2232
|
+
# The betweenness centrality of the input list of vertices within the input graph. The values are in the range 0 to 1.
|
2233
|
+
|
2234
|
+
# """
|
2235
|
+
# import warnings
|
2236
|
+
|
2237
|
+
# try:
|
2238
|
+
# import networkx as nx
|
2239
|
+
# except:
|
2240
|
+
# print("Graph.BetwennessCentrality - Information: Installing required networkx library.")
|
2241
|
+
# try:
|
2242
|
+
# os.system("pip install networkx")
|
2243
|
+
# except:
|
2244
|
+
# os.system("pip install networkx --user")
|
2245
|
+
# try:
|
2246
|
+
# import networkx as nx
|
2247
|
+
# print("Graph.BetwennessCentrality - Infromation: networkx library installed correctly.")
|
2248
|
+
# except:
|
2249
|
+
# warnings.warn("Graph.BetwennessCentrality - Error: Could not import networkx. Please try to install networkx manually. Returning None.")
|
2250
|
+
# return None
|
2251
|
+
|
2252
|
+
# from topologicpy.Dictionary import Dictionary
|
2253
|
+
# from topologicpy.Color import Color
|
2254
|
+
# from topologicpy.Topology import Topology
|
2255
|
+
# from topologicpy.Helper import Helper
|
2256
|
+
|
2257
|
+
# if weightKey:
|
2258
|
+
# if "len" in weightKey.lower() or "dis" in weightKey.lower():
|
2259
|
+
# weightKey = "length"
|
2260
|
+
# nx_graph = Graph.NetworkXGraph(graph)
|
2261
|
+
# if "vert" in method.lower():
|
2262
|
+
# elements = Graph.Vertices(graph)
|
2263
|
+
# elements_dict = nx.betweenness_centrality(nx_graph, normalized=normalize, weight=weightKey)
|
2264
|
+
# values = [round(value, mantissa) for value in list(elements_dict.values())]
|
2265
|
+
# else:
|
2266
|
+
# elements = Graph.Edges(graph)
|
2267
|
+
# elements_dict = nx.edge_betweenness_centrality(nx_graph, normalized=normalize, weight=weightKey)
|
2268
|
+
# values = [round(value, mantissa) for value in list(elements_dict.values())]
|
2269
|
+
# if nxCompatible == False:
|
2270
|
+
# if mantissa > 0: # We cannot have values in the range 0 to 1 with a mantissa < 1
|
2271
|
+
# values = [round(v, mantissa) for v in Helper.Normalize(values)]
|
2272
|
+
# else:
|
2273
|
+
# values = Helper.Normalize(values)
|
2274
|
+
# min_value = 0
|
2275
|
+
# max_value = 1
|
2276
|
+
# else:
|
2277
|
+
# min_value = min(values)
|
2278
|
+
# max_value = max(values)
|
2279
|
+
|
2280
|
+
# for i, value in enumerate(values):
|
2281
|
+
# d = Topology.Dictionary(elements[i])
|
2282
|
+
# color = Color.AnyToHex(Color.ByValueInRange(value, minValue=min_value, maxValue=max_value, colorScale=colorScale))
|
2283
|
+
# d = Dictionary.SetValuesAtKeys(d, [key, colorKey], [value, color])
|
2284
|
+
# elements[i] = Topology.SetDictionary(elements[i], d)
|
2285
|
+
|
2286
|
+
# return values
|
1993
2287
|
|
1994
2288
|
@staticmethod
|
1995
2289
|
def BetweennessPartition(graph, n=2, m=10, key="partition", tolerance=0.0001, silent=False):
|
@@ -3246,432 +3540,432 @@ class Graph:
|
|
3246
3540
|
edges = get_edges(relationships, vertices)
|
3247
3541
|
return Graph.ByVerticesEdges(vertices, edges)
|
3248
3542
|
|
3249
|
-
@staticmethod
|
3250
|
-
def ByIFCFile_old(file,
|
3251
|
-
|
3252
|
-
|
3253
|
-
|
3254
|
-
|
3255
|
-
|
3256
|
-
|
3257
|
-
|
3258
|
-
|
3259
|
-
|
3260
|
-
|
3261
|
-
|
3262
|
-
|
3263
|
-
|
3543
|
+
# @staticmethod
|
3544
|
+
# def ByIFCFile_old(file,
|
3545
|
+
# includeTypes: list = [],
|
3546
|
+
# excludeTypes: list = [],
|
3547
|
+
# includeRels: list = [],
|
3548
|
+
# excludeRels: list = [],
|
3549
|
+
# transferDictionaries: bool = False,
|
3550
|
+
# useInternalVertex: bool = False,
|
3551
|
+
# storeBREP: bool = False,
|
3552
|
+
# removeCoplanarFaces: bool = False,
|
3553
|
+
# xMin: float = -0.5, yMin: float = -0.5, zMin: float = -0.5,
|
3554
|
+
# xMax: float = 0.5, yMax: float = 0.5, zMax: float = 0.5,
|
3555
|
+
# tolerance: float = 0.0001):
|
3556
|
+
# """
|
3557
|
+
# Create a Graph from an IFC file. This code is partially based on code from Bruno Postle.
|
3264
3558
|
|
3265
|
-
|
3266
|
-
|
3267
|
-
|
3268
|
-
|
3269
|
-
|
3270
|
-
|
3271
|
-
|
3272
|
-
|
3273
|
-
|
3274
|
-
|
3275
|
-
|
3276
|
-
|
3277
|
-
|
3278
|
-
|
3279
|
-
|
3280
|
-
|
3281
|
-
|
3282
|
-
|
3283
|
-
|
3284
|
-
|
3285
|
-
|
3286
|
-
|
3287
|
-
|
3288
|
-
|
3289
|
-
|
3290
|
-
|
3291
|
-
|
3292
|
-
|
3293
|
-
|
3294
|
-
|
3295
|
-
|
3296
|
-
|
3297
|
-
|
3298
|
-
|
3299
|
-
|
3300
|
-
Returns
|
3301
|
-
-------
|
3302
|
-
topologic_core.Graph
|
3303
|
-
The created graph.
|
3559
|
+
# Parameters
|
3560
|
+
# ----------
|
3561
|
+
# file : file
|
3562
|
+
# The input IFC file
|
3563
|
+
# includeTypes : list , optional
|
3564
|
+
# A list of IFC object types to include in the graph. Default is [] which means all object types are included.
|
3565
|
+
# excludeTypes : list , optional
|
3566
|
+
# A list of IFC object types to exclude from the graph. Default is [] which mean no object type is excluded.
|
3567
|
+
# includeRels : list , optional
|
3568
|
+
# A list of IFC relationship types to include in the graph. Default is [] which means all relationship types are included.
|
3569
|
+
# excludeRels : list , optional
|
3570
|
+
# A list of IFC relationship types to exclude from the graph. Default is [] which mean no relationship type is excluded.
|
3571
|
+
# transferDictionaries : bool , optional
|
3572
|
+
# If set to True, the dictionaries from the IFC file will be transferred to the topology. Otherwise, they won't. Default is False.
|
3573
|
+
# useInternalVertex : bool , optional
|
3574
|
+
# If set to True, use an internal vertex to represent the subtopology. Otherwise, use its centroid. Default is False.
|
3575
|
+
# storeBREP : bool , optional
|
3576
|
+
# If set to True, store the BRep of the subtopology in its representative vertex. Default is False.
|
3577
|
+
# removeCoplanarFaces : bool , optional
|
3578
|
+
# If set to True, coplanar faces are removed. Otherwise they are not. Default is False.
|
3579
|
+
# xMin : float, optional
|
3580
|
+
# The desired minimum value to assign for a vertex's X coordinate. Default is -0.5.
|
3581
|
+
# yMin : float, optional
|
3582
|
+
# The desired minimum value to assign for a vertex's Y coordinate. Default is -0.5.
|
3583
|
+
# zMin : float, optional
|
3584
|
+
# The desired minimum value to assign for a vertex's Z coordinate. Default is -0.5.
|
3585
|
+
# xMax : float, optional
|
3586
|
+
# The desired maximum value to assign for a vertex's X coordinate. Default is 0.5.
|
3587
|
+
# yMax : float, optional
|
3588
|
+
# The desired maximum value to assign for a vertex's Y coordinate. Default is 0.5.
|
3589
|
+
# zMax : float, optional
|
3590
|
+
# The desired maximum value to assign for a vertex's Z coordinate. Default is 0.5.
|
3591
|
+
# tolerance : float , optional
|
3592
|
+
# The desired tolerance. Default is 0.0001.
|
3304
3593
|
|
3305
|
-
|
3306
|
-
|
3307
|
-
|
3308
|
-
|
3309
|
-
from topologicpy.Graph import Graph
|
3310
|
-
from topologicpy.Dictionary import Dictionary
|
3311
|
-
try:
|
3312
|
-
import ifcopenshell
|
3313
|
-
import ifcopenshell.util.placement
|
3314
|
-
import ifcopenshell.util.element
|
3315
|
-
import ifcopenshell.util.shape
|
3316
|
-
import ifcopenshell.geom
|
3317
|
-
except:
|
3318
|
-
print("Graph.ByIFCFile - Warning: Installing required ifcopenshell library.")
|
3319
|
-
try:
|
3320
|
-
os.system("pip install ifcopenshell")
|
3321
|
-
except:
|
3322
|
-
os.system("pip install ifcopenshell --user")
|
3323
|
-
try:
|
3324
|
-
import ifcopenshell
|
3325
|
-
import ifcopenshell.util.placement
|
3326
|
-
import ifcopenshell.util.element
|
3327
|
-
import ifcopenshell.util.shape
|
3328
|
-
import ifcopenshell.geom
|
3329
|
-
print("Graph.ByIFCFile - Warning: ifcopenshell library installed correctly.")
|
3330
|
-
except:
|
3331
|
-
warnings.warn("Graph.ByIFCFile - Error: Could not import ifcopenshell. Please try to install ifcopenshell manually. Returning None.")
|
3332
|
-
return None
|
3594
|
+
# Returns
|
3595
|
+
# -------
|
3596
|
+
# topologic_core.Graph
|
3597
|
+
# The created graph.
|
3333
3598
|
|
3334
|
-
|
3335
|
-
|
3336
|
-
|
3337
|
-
|
3338
|
-
|
3339
|
-
|
3340
|
-
|
3341
|
-
|
3342
|
-
|
3343
|
-
|
3344
|
-
|
3345
|
-
|
3346
|
-
|
3347
|
-
|
3348
|
-
|
3349
|
-
|
3350
|
-
|
3351
|
-
|
3352
|
-
|
3353
|
-
|
3354
|
-
|
3355
|
-
|
3356
|
-
|
3357
|
-
|
3358
|
-
|
3359
|
-
|
3360
|
-
|
3361
|
-
|
3362
|
-
|
3363
|
-
|
3364
|
-
|
3365
|
-
|
3366
|
-
|
3367
|
-
|
3368
|
-
|
3369
|
-
|
3370
|
-
|
3371
|
-
|
3372
|
-
def IFCRelationships(ifc_file, include=[], exclude=[]):
|
3373
|
-
include = [s.lower() for s in include]
|
3374
|
-
exclude = [s.lower() for s in exclude]
|
3375
|
-
rel_types = [ifc_rel.is_a() for ifc_rel in ifc_file.by_type("IfcRelationship")]
|
3376
|
-
rel_types = list(set(rel_types))
|
3377
|
-
relationships = []
|
3378
|
-
for ifc_rel in ifc_file.by_type("IfcRelationship"):
|
3379
|
-
rel_type = ifc_rel.is_a().lower()
|
3380
|
-
if rel_type in exclude:
|
3381
|
-
continue
|
3382
|
-
if rel_type in include or len(include) == 0:
|
3383
|
-
relationships.append(ifc_rel)
|
3384
|
-
return relationships
|
3599
|
+
# """
|
3600
|
+
# from topologicpy.Topology import Topology
|
3601
|
+
# from topologicpy.Vertex import Vertex
|
3602
|
+
# from topologicpy.Edge import Edge
|
3603
|
+
# from topologicpy.Graph import Graph
|
3604
|
+
# from topologicpy.Dictionary import Dictionary
|
3605
|
+
# try:
|
3606
|
+
# import ifcopenshell
|
3607
|
+
# import ifcopenshell.util.placement
|
3608
|
+
# import ifcopenshell.util.element
|
3609
|
+
# import ifcopenshell.util.shape
|
3610
|
+
# import ifcopenshell.geom
|
3611
|
+
# except:
|
3612
|
+
# print("Graph.ByIFCFile - Warning: Installing required ifcopenshell library.")
|
3613
|
+
# try:
|
3614
|
+
# os.system("pip install ifcopenshell")
|
3615
|
+
# except:
|
3616
|
+
# os.system("pip install ifcopenshell --user")
|
3617
|
+
# try:
|
3618
|
+
# import ifcopenshell
|
3619
|
+
# import ifcopenshell.util.placement
|
3620
|
+
# import ifcopenshell.util.element
|
3621
|
+
# import ifcopenshell.util.shape
|
3622
|
+
# import ifcopenshell.geom
|
3623
|
+
# print("Graph.ByIFCFile - Warning: ifcopenshell library installed correctly.")
|
3624
|
+
# except:
|
3625
|
+
# warnings.warn("Graph.ByIFCFile - Error: Could not import ifcopenshell. Please try to install ifcopenshell manually. Returning None.")
|
3626
|
+
# return None
|
3627
|
+
|
3628
|
+
# import random
|
3629
|
+
|
3630
|
+
# def vertexAtKeyValue(vertices, key, value):
|
3631
|
+
# for v in vertices:
|
3632
|
+
# d = Topology.Dictionary(v)
|
3633
|
+
# d_value = Dictionary.ValueAtKey(d, key)
|
3634
|
+
# if value == d_value:
|
3635
|
+
# return v
|
3636
|
+
# return None
|
3385
3637
|
|
3386
|
-
|
3387
|
-
|
3388
|
-
|
3638
|
+
# def IFCObjects(ifc_file, include=[], exclude=[]):
|
3639
|
+
# include = [s.lower() for s in include]
|
3640
|
+
# exclude = [s.lower() for s in exclude]
|
3641
|
+
# all_objects = ifc_file.by_type('IfcProduct')
|
3642
|
+
# return_objects = []
|
3643
|
+
# for obj in all_objects:
|
3644
|
+
# is_a = obj.is_a().lower()
|
3645
|
+
# if is_a in exclude:
|
3646
|
+
# continue
|
3647
|
+
# if is_a in include or len(include) == 0:
|
3648
|
+
# return_objects.append(obj)
|
3649
|
+
# return return_objects
|
3650
|
+
|
3651
|
+
# def IFCObjectTypes(ifc_file):
|
3652
|
+
# products = IFCObjects(ifc_file)
|
3653
|
+
# obj_types = []
|
3654
|
+
# for product in products:
|
3655
|
+
# obj_types.append(product.is_a())
|
3656
|
+
# obj_types = list(set(obj_types))
|
3657
|
+
# obj_types.sort()
|
3658
|
+
# return obj_types
|
3659
|
+
|
3660
|
+
# def IFCRelationshipTypes(ifc_file):
|
3661
|
+
# rel_types = [ifc_rel.is_a() for ifc_rel in ifc_file.by_type("IfcRelationship")]
|
3662
|
+
# rel_types = list(set(rel_types))
|
3663
|
+
# rel_types.sort()
|
3664
|
+
# return rel_types
|
3665
|
+
|
3666
|
+
# def IFCRelationships(ifc_file, include=[], exclude=[]):
|
3667
|
+
# include = [s.lower() for s in include]
|
3668
|
+
# exclude = [s.lower() for s in exclude]
|
3669
|
+
# rel_types = [ifc_rel.is_a() for ifc_rel in ifc_file.by_type("IfcRelationship")]
|
3670
|
+
# rel_types = list(set(rel_types))
|
3671
|
+
# relationships = []
|
3672
|
+
# for ifc_rel in ifc_file.by_type("IfcRelationship"):
|
3673
|
+
# rel_type = ifc_rel.is_a().lower()
|
3674
|
+
# if rel_type in exclude:
|
3675
|
+
# continue
|
3676
|
+
# if rel_type in include or len(include) == 0:
|
3677
|
+
# relationships.append(ifc_rel)
|
3678
|
+
# return relationships
|
3679
|
+
|
3680
|
+
# def get_psets(entity):
|
3681
|
+
# # Initialize the PSET dictionary for this entity
|
3682
|
+
# psets = {}
|
3389
3683
|
|
3390
|
-
|
3391
|
-
|
3392
|
-
|
3684
|
+
# # Check if the entity has a GlobalId
|
3685
|
+
# if not hasattr(entity, 'GlobalId'):
|
3686
|
+
# raise ValueError("The provided entity does not have a GlobalId.")
|
3393
3687
|
|
3394
|
-
|
3395
|
-
|
3396
|
-
|
3397
|
-
|
3688
|
+
# # Get the property sets related to this entity
|
3689
|
+
# for definition in entity.IsDefinedBy:
|
3690
|
+
# if definition.is_a('IfcRelDefinesByProperties'):
|
3691
|
+
# property_set = definition.RelatingPropertyDefinition
|
3398
3692
|
|
3399
|
-
|
3400
|
-
|
3401
|
-
|
3402
|
-
|
3693
|
+
# # Check if it is a property set
|
3694
|
+
# if not property_set == None:
|
3695
|
+
# if property_set.is_a('IfcPropertySet'):
|
3696
|
+
# pset_name = "IFC_"+property_set.Name
|
3403
3697
|
|
3404
|
-
|
3405
|
-
|
3698
|
+
# # Dictionary to hold individual properties
|
3699
|
+
# properties = {}
|
3406
3700
|
|
3407
|
-
|
3408
|
-
|
3409
|
-
|
3410
|
-
|
3411
|
-
|
3412
|
-
|
3413
|
-
|
3701
|
+
# # Iterate over the properties in the PSET
|
3702
|
+
# for prop in property_set.HasProperties:
|
3703
|
+
# if prop.is_a('IfcPropertySingleValue'):
|
3704
|
+
# # Get the property name and value
|
3705
|
+
# prop_name = "IFC_"+prop.Name
|
3706
|
+
# prop_value = prop.NominalValue.wrappedValue if prop.NominalValue else None
|
3707
|
+
# properties[prop_name] = prop_value
|
3414
3708
|
|
3415
|
-
|
3416
|
-
|
3417
|
-
|
3709
|
+
# # Add this PSET to the dictionary for this entity
|
3710
|
+
# psets[pset_name] = properties
|
3711
|
+
# return psets
|
3418
3712
|
|
3419
|
-
|
3420
|
-
|
3713
|
+
# def get_color_transparency_material(entity):
|
3714
|
+
# import random
|
3421
3715
|
|
3422
|
-
|
3423
|
-
|
3424
|
-
|
3425
|
-
|
3716
|
+
# # Set default Material Name and ID
|
3717
|
+
# material_list = []
|
3718
|
+
# # Set default transparency based on entity type or material
|
3719
|
+
# default_transparency = 0.0
|
3426
3720
|
|
3427
|
-
|
3428
|
-
|
3429
|
-
|
3430
|
-
|
3431
|
-
|
3432
|
-
|
3721
|
+
# # Check if the entity is an opening or made of glass
|
3722
|
+
# is_a = entity.is_a().lower()
|
3723
|
+
# if "opening" in is_a or "window" in is_a or "door" in is_a or "space" in is_a:
|
3724
|
+
# default_transparency = 0.7
|
3725
|
+
# elif "space" in is_a:
|
3726
|
+
# default_transparency = 0.8
|
3433
3727
|
|
3434
|
-
|
3435
|
-
|
3436
|
-
|
3437
|
-
|
3438
|
-
|
3439
|
-
|
3440
|
-
|
3441
|
-
|
3442
|
-
|
3443
|
-
|
3444
|
-
|
3445
|
-
|
3446
|
-
|
3447
|
-
|
3448
|
-
|
3449
|
-
|
3450
|
-
|
3451
|
-
|
3728
|
+
# # Check if the entity has constituent materials (e.g., glass)
|
3729
|
+
# else:
|
3730
|
+
# # Check for associated materials (ConstituentMaterial or direct material assignment)
|
3731
|
+
# materials_checked = False
|
3732
|
+
# if hasattr(entity, 'HasAssociations'):
|
3733
|
+
# for rel in entity.HasAssociations:
|
3734
|
+
# if rel.is_a('IfcRelAssociatesMaterial'):
|
3735
|
+
# material = rel.RelatingMaterial
|
3736
|
+
# if material.is_a('IfcMaterial') and 'glass' in material.Name.lower():
|
3737
|
+
# default_transparency = 0.5
|
3738
|
+
# materials_checked = True
|
3739
|
+
# elif material.is_a('IfcMaterialLayerSetUsage'):
|
3740
|
+
# material_layers = material.ForLayerSet.MaterialLayers
|
3741
|
+
# for layer in material_layers:
|
3742
|
+
# material_list.append(layer.Material.Name)
|
3743
|
+
# if 'glass' in layer.Material.Name.lower():
|
3744
|
+
# default_transparency = 0.5
|
3745
|
+
# materials_checked = True
|
3452
3746
|
|
3453
|
-
|
3454
|
-
|
3455
|
-
|
3456
|
-
|
3457
|
-
|
3458
|
-
|
3459
|
-
|
3460
|
-
|
3461
|
-
|
3462
|
-
|
3463
|
-
|
3464
|
-
|
3465
|
-
|
3466
|
-
|
3467
|
-
|
3468
|
-
|
3469
|
-
|
3470
|
-
|
3471
|
-
|
3472
|
-
|
3473
|
-
|
3474
|
-
|
3475
|
-
|
3476
|
-
|
3477
|
-
|
3478
|
-
|
3479
|
-
|
3480
|
-
|
3481
|
-
|
3482
|
-
|
3483
|
-
|
3484
|
-
|
3485
|
-
|
3486
|
-
|
3487
|
-
|
3488
|
-
|
3489
|
-
|
3490
|
-
|
3491
|
-
|
3747
|
+
# # Check for ConstituentMaterial if available
|
3748
|
+
# if hasattr(entity, 'HasAssociations') and not materials_checked:
|
3749
|
+
# for rel in entity.HasAssociations:
|
3750
|
+
# if rel.is_a('IfcRelAssociatesMaterial'):
|
3751
|
+
# material = rel.RelatingMaterial
|
3752
|
+
# if material.is_a('IfcMaterialConstituentSet'):
|
3753
|
+
# for constituent in material.MaterialConstituents:
|
3754
|
+
# material_list.append(constituent.Material.Name)
|
3755
|
+
# if 'glass' in constituent.Material.Name.lower():
|
3756
|
+
# default_transparency = 0.5
|
3757
|
+
# materials_checked = True
|
3758
|
+
|
3759
|
+
# # Check if the entity has ShapeAspects with associated materials or styles
|
3760
|
+
# if hasattr(entity, 'HasShapeAspects') and not materials_checked:
|
3761
|
+
# for shape_aspect in entity.HasShapeAspects:
|
3762
|
+
# if hasattr(shape_aspect, 'StyledByItem') and shape_aspect.StyledByItem:
|
3763
|
+
# for styled_item in shape_aspect.StyledByItem:
|
3764
|
+
# for style in styled_item.Styles:
|
3765
|
+
# if style.is_a('IfcSurfaceStyle'):
|
3766
|
+
# for surface_style in style.Styles:
|
3767
|
+
# if surface_style.is_a('IfcSurfaceStyleRendering'):
|
3768
|
+
# transparency = getattr(surface_style, 'Transparency', default_transparency)
|
3769
|
+
# if transparency > 0:
|
3770
|
+
# default_transparency = transparency
|
3771
|
+
|
3772
|
+
# # Try to get the actual color and transparency if defined
|
3773
|
+
# if hasattr(entity, 'Representation') and entity.Representation:
|
3774
|
+
# for rep in entity.Representation.Representations:
|
3775
|
+
# for item in rep.Items:
|
3776
|
+
# if hasattr(item, 'StyledByItem') and item.StyledByItem:
|
3777
|
+
# for styled_item in item.StyledByItem:
|
3778
|
+
# if hasattr(styled_item, 'Styles'):
|
3779
|
+
# for style in styled_item.Styles:
|
3780
|
+
# if style.is_a('IfcSurfaceStyle'):
|
3781
|
+
# for surface_style in style.Styles:
|
3782
|
+
# if surface_style.is_a('IfcSurfaceStyleRendering'):
|
3783
|
+
# color = surface_style.SurfaceColour
|
3784
|
+
# transparency = getattr(surface_style, 'Transparency', default_transparency)
|
3785
|
+
# return (color.Red*255, color.Green*255, color.Blue*255), transparency, material_list
|
3492
3786
|
|
3493
|
-
|
3494
|
-
|
3495
|
-
|
3496
|
-
|
3497
|
-
|
3498
|
-
|
3499
|
-
|
3500
|
-
|
3501
|
-
|
3502
|
-
|
3787
|
+
# # If no color is defined, return a consistent random color based on the entity type
|
3788
|
+
# if "wall" in is_a:
|
3789
|
+
# color = (175, 175, 175)
|
3790
|
+
# elif "slab" in is_a:
|
3791
|
+
# color = (200, 200, 200)
|
3792
|
+
# elif "space" in is_a:
|
3793
|
+
# color = (250, 250, 250)
|
3794
|
+
# else:
|
3795
|
+
# random.seed(hash(is_a))
|
3796
|
+
# color = (random.random(), random.random(), random.random())
|
3503
3797
|
|
3504
|
-
|
3798
|
+
# return color, default_transparency, material_list
|
3505
3799
|
|
3506
|
-
|
3507
|
-
|
3508
|
-
|
3509
|
-
|
3510
|
-
|
3511
|
-
|
3512
|
-
|
3513
|
-
|
3514
|
-
|
3515
|
-
|
3516
|
-
|
3517
|
-
|
3518
|
-
|
3519
|
-
|
3520
|
-
|
3521
|
-
|
3522
|
-
|
3523
|
-
|
3524
|
-
|
3525
|
-
|
3526
|
-
|
3527
|
-
|
3528
|
-
|
3529
|
-
|
3530
|
-
|
3531
|
-
|
3532
|
-
|
3533
|
-
|
3534
|
-
|
3535
|
-
|
3536
|
-
|
3537
|
-
|
3538
|
-
|
3539
|
-
|
3540
|
-
|
3541
|
-
|
3542
|
-
|
3800
|
+
# def vertexByIFCObject(ifc_object, object_types, restrict=False):
|
3801
|
+
# settings = ifcopenshell.geom.settings()
|
3802
|
+
# settings.set(settings.USE_WORLD_COORDS,True)
|
3803
|
+
# try:
|
3804
|
+
# shape = ifcopenshell.geom.create_shape(settings, ifc_object)
|
3805
|
+
# except:
|
3806
|
+
# shape = None
|
3807
|
+
# if shape or restrict == False: #Only add vertices of entities that have 3D geometries.
|
3808
|
+
# obj_id = ifc_object.id()
|
3809
|
+
# psets = ifcopenshell.util.element.get_psets(ifc_object)
|
3810
|
+
# obj_type = ifc_object.is_a()
|
3811
|
+
# obj_type_id = object_types.index(obj_type)
|
3812
|
+
# name = "Untitled"
|
3813
|
+
# LongName = "Untitled"
|
3814
|
+
# try:
|
3815
|
+
# name = ifc_object.Name
|
3816
|
+
# except:
|
3817
|
+
# name = "Untitled"
|
3818
|
+
# try:
|
3819
|
+
# LongName = ifc_object.LongName
|
3820
|
+
# except:
|
3821
|
+
# LongName = name
|
3822
|
+
|
3823
|
+
# if name == None:
|
3824
|
+
# name = "Untitled"
|
3825
|
+
# if LongName == None:
|
3826
|
+
# LongName = "Untitled"
|
3827
|
+
# label = str(obj_id)+" "+LongName+" ("+obj_type+" "+str(obj_type_id)+")"
|
3828
|
+
# try:
|
3829
|
+
# grouped_verts = ifcopenshell.util.shape.get_vertices(shape.geometry)
|
3830
|
+
# vertices = [Vertex.ByCoordinates(list(coords)) for coords in grouped_verts]
|
3831
|
+
# centroid = Vertex.Centroid(vertices)
|
3832
|
+
# except:
|
3833
|
+
# x = random.uniform(xMin,xMax)
|
3834
|
+
# y = random.uniform(yMin,yMax)
|
3835
|
+
# z = random.uniform(zMin,zMax)
|
3836
|
+
# centroid = Vertex.ByCoordinates(x, y, z)
|
3543
3837
|
|
3544
|
-
|
3545
|
-
|
3546
|
-
|
3547
|
-
|
3548
|
-
|
3549
|
-
|
3550
|
-
|
3551
|
-
|
3552
|
-
|
3553
|
-
|
3554
|
-
|
3555
|
-
|
3556
|
-
|
3557
|
-
|
3558
|
-
|
3559
|
-
|
3560
|
-
|
3561
|
-
|
3562
|
-
|
3563
|
-
|
3564
|
-
|
3565
|
-
|
3566
|
-
|
3567
|
-
|
3568
|
-
|
3569
|
-
|
3570
|
-
|
3571
|
-
|
3572
|
-
|
3573
|
-
|
3574
|
-
|
3575
|
-
|
3576
|
-
|
3577
|
-
|
3578
|
-
|
3579
|
-
|
3580
|
-
|
3581
|
-
|
3582
|
-
|
3583
|
-
|
3584
|
-
|
3585
|
-
|
3586
|
-
|
3587
|
-
|
3588
|
-
|
3589
|
-
|
3590
|
-
|
3591
|
-
|
3592
|
-
|
3593
|
-
|
3594
|
-
|
3595
|
-
|
3596
|
-
def edgesByIFCRelationships(ifc_relationships, ifc_types, vertices):
|
3597
|
-
tuples = []
|
3598
|
-
edges = []
|
3838
|
+
# # Store relevant information
|
3839
|
+
# if transferDictionaries == True:
|
3840
|
+
# color, transparency, material_list = get_color_transparency_material(ifc_object)
|
3841
|
+
# if color == None:
|
3842
|
+
# color = "white"
|
3843
|
+
# if transparency == None:
|
3844
|
+
# transparency = 0
|
3845
|
+
# entity_dict = {
|
3846
|
+
# "TOPOLOGIC_id": str(Topology.UUID(centroid)),
|
3847
|
+
# "TOPOLOGIC_name": getattr(ifc_object, 'Name', "Untitled"),
|
3848
|
+
# "TOPOLOGIC_type": Topology.TypeAsString(centroid),
|
3849
|
+
# "TOPOLOGIC_color": color,
|
3850
|
+
# "TOPOLOGIC_opacity": 1.0 - transparency,
|
3851
|
+
# "IFC_global_id": getattr(ifc_object, 'GlobalId', 0),
|
3852
|
+
# "IFC_name": getattr(ifc_object, 'Name', "Untitled"),
|
3853
|
+
# "IFC_type": ifc_object.is_a(),
|
3854
|
+
# "IFC_material_list": material_list,
|
3855
|
+
# }
|
3856
|
+
# topology_dict = Dictionary.ByPythonDictionary(entity_dict)
|
3857
|
+
# # Get PSETs dictionary
|
3858
|
+
# pset_python_dict = get_psets(ifc_object)
|
3859
|
+
# pset_dict = Dictionary.ByPythonDictionary(pset_python_dict)
|
3860
|
+
# topology_dict = Dictionary.ByMergedDictionaries([topology_dict, pset_dict])
|
3861
|
+
# if storeBREP == True or useInternalVertex == True:
|
3862
|
+
# shape_topology = None
|
3863
|
+
# if hasattr(ifc_object, "Representation") and ifc_object.Representation:
|
3864
|
+
# for rep in ifc_object.Representation.Representations:
|
3865
|
+
# if rep.is_a("IfcShapeRepresentation"):
|
3866
|
+
# try:
|
3867
|
+
# # Generate the geometry for this entity
|
3868
|
+
# shape = ifcopenshell.geom.create_shape(settings, ifc_object)
|
3869
|
+
# # Get grouped vertices and grouped faces
|
3870
|
+
# grouped_verts = shape.geometry.verts
|
3871
|
+
# verts = [ [grouped_verts[i], grouped_verts[i + 1], grouped_verts[i + 2]] for i in range(0, len(grouped_verts), 3)]
|
3872
|
+
# grouped_edges = shape.geometry.edges
|
3873
|
+
# edges = [[grouped_edges[i], grouped_edges[i + 1]] for i in range(0, len(grouped_edges), 2)]
|
3874
|
+
# grouped_faces = shape.geometry.faces
|
3875
|
+
# faces = [ [grouped_faces[i], grouped_faces[i + 1], grouped_faces[i + 2]] for i in range(0, len(grouped_faces), 3)]
|
3876
|
+
# shape_topology = Topology.ByGeometry(verts, edges, faces, silent=True)
|
3877
|
+
# if not shape_topology == None:
|
3878
|
+
# if removeCoplanarFaces == True:
|
3879
|
+
# shape_topology = Topology.RemoveCoplanarFaces(shape_topology, epsilon=0.0001)
|
3880
|
+
# except:
|
3881
|
+
# pass
|
3882
|
+
# if not shape_topology == None and storeBREP:
|
3883
|
+
# topology_dict = Dictionary.SetValuesAtKeys(topology_dict, ["brep", "brepType", "brepTypeString"], [Topology.BREPString(shape_topology), Topology.Type(shape_topology), Topology.TypeAsString(shape_topology)])
|
3884
|
+
# if not shape_topology == None and useInternalVertex == True:
|
3885
|
+
# centroid = Topology.InternalVertex(shape_topology)
|
3886
|
+
# centroid = Topology.SetDictionary(centroid, topology_dict)
|
3887
|
+
# return centroid
|
3888
|
+
# return None
|
3599
3889
|
|
3600
|
-
|
3601
|
-
|
3602
|
-
|
3603
|
-
|
3604
|
-
|
3605
|
-
|
3606
|
-
|
3607
|
-
|
3608
|
-
|
3609
|
-
|
3610
|
-
|
3611
|
-
|
3612
|
-
|
3613
|
-
|
3614
|
-
|
3615
|
-
|
3616
|
-
|
3617
|
-
|
3618
|
-
|
3619
|
-
|
3620
|
-
|
3621
|
-
|
3622
|
-
|
3623
|
-
|
3624
|
-
|
3625
|
-
|
3626
|
-
|
3627
|
-
|
3628
|
-
|
3629
|
-
|
3630
|
-
|
3631
|
-
|
3632
|
-
|
3633
|
-
|
3634
|
-
|
3635
|
-
|
3636
|
-
|
3637
|
-
|
3638
|
-
|
3639
|
-
|
3640
|
-
|
3641
|
-
|
3642
|
-
|
3643
|
-
|
3644
|
-
|
3645
|
-
|
3646
|
-
|
3647
|
-
|
3648
|
-
|
3649
|
-
|
3650
|
-
|
3651
|
-
|
3652
|
-
|
3653
|
-
|
3654
|
-
|
3655
|
-
|
3656
|
-
|
3657
|
-
|
3658
|
-
|
3659
|
-
|
3660
|
-
|
3661
|
-
|
3662
|
-
|
3663
|
-
|
3664
|
-
|
3665
|
-
|
3666
|
-
|
3667
|
-
|
3668
|
-
|
3669
|
-
|
3670
|
-
|
3671
|
-
|
3672
|
-
|
3673
|
-
|
3674
|
-
|
3890
|
+
# def edgesByIFCRelationships(ifc_relationships, ifc_types, vertices):
|
3891
|
+
# tuples = []
|
3892
|
+
# edges = []
|
3893
|
+
|
3894
|
+
# for ifc_rel in ifc_relationships:
|
3895
|
+
# source = None
|
3896
|
+
# destinations = []
|
3897
|
+
# if ifc_rel.is_a("IfcRelConnectsPorts"):
|
3898
|
+
# source = ifc_rel.RelatingPort
|
3899
|
+
# destinations = ifc_rel.RelatedPorts
|
3900
|
+
# elif ifc_rel.is_a("IfcRelConnectsPortToElement"):
|
3901
|
+
# source = ifc_rel.RelatingPort
|
3902
|
+
# destinations = [ifc_rel.RelatedElement]
|
3903
|
+
# elif ifc_rel.is_a("IfcRelAggregates"):
|
3904
|
+
# source = ifc_rel.RelatingObject
|
3905
|
+
# destinations = ifc_rel.RelatedObjects
|
3906
|
+
# elif ifc_rel.is_a("IfcRelNests"):
|
3907
|
+
# source = ifc_rel.RelatingObject
|
3908
|
+
# destinations = ifc_rel.RelatedObjects
|
3909
|
+
# elif ifc_rel.is_a("IfcRelAssignsToGroup"):
|
3910
|
+
# source = ifc_rel.RelatingGroup
|
3911
|
+
# destinations = ifc_rel.RelatedObjects
|
3912
|
+
# elif ifc_rel.is_a("IfcRelConnectsPathElements"):
|
3913
|
+
# source = ifc_rel.RelatingElement
|
3914
|
+
# destinations = [ifc_rel.RelatedElement]
|
3915
|
+
# elif ifc_rel.is_a("IfcRelConnectsStructuralMember"):
|
3916
|
+
# source = ifc_rel.RelatingStructuralMember
|
3917
|
+
# destinations = [ifc_rel.RelatedStructuralConnection]
|
3918
|
+
# elif ifc_rel.is_a("IfcRelContainedInSpatialStructure"):
|
3919
|
+
# source = ifc_rel.RelatingStructure
|
3920
|
+
# destinations = ifc_rel.RelatedElements
|
3921
|
+
# elif ifc_rel.is_a("IfcRelFillsElement"):
|
3922
|
+
# source = ifc_rel.RelatingOpeningElement
|
3923
|
+
# destinations = [ifc_rel.RelatedBuildingElement]
|
3924
|
+
# elif ifc_rel.is_a("IfcRelSpaceBoundary"):
|
3925
|
+
# source = ifc_rel.RelatingSpace
|
3926
|
+
# destinations = [ifc_rel.RelatedBuildingElement]
|
3927
|
+
# elif ifc_rel.is_a("IfcRelVoidsElement"):
|
3928
|
+
# source = ifc_rel.RelatingBuildingElement
|
3929
|
+
# destinations = [ifc_rel.RelatedOpeningElement]
|
3930
|
+
# elif ifc_rel.is_a("IfcRelDefinesByProperties") or ifc_rel.is_a("IfcRelAssociatesMaterial") or ifc_rel.is_a("IfcRelDefinesByType"):
|
3931
|
+
# source = None
|
3932
|
+
# destinations = None
|
3933
|
+
# else:
|
3934
|
+
# print("Graph.ByIFCFile - Warning: The relationship", ifc_rel, "is not supported. Skipping.")
|
3935
|
+
# if source:
|
3936
|
+
# sv = vertexAtKeyValue(vertices, key="IFC_global_id", value=getattr(source, 'GlobalId', 0))
|
3937
|
+
# if sv:
|
3938
|
+
# si = Vertex.Index(sv, vertices, tolerance=tolerance)
|
3939
|
+
# if not si == None:
|
3940
|
+
# for destination in destinations:
|
3941
|
+
# if destination == None:
|
3942
|
+
# continue
|
3943
|
+
# ev = vertexAtKeyValue(vertices, key="IFC_global_id", value=getattr(destination, 'GlobalId', 0),)
|
3944
|
+
# if ev:
|
3945
|
+
# ei = Vertex.Index(ev, vertices, tolerance=tolerance)
|
3946
|
+
# if not ei == None:
|
3947
|
+
# if not([si,ei] in tuples or [ei,si] in tuples):
|
3948
|
+
# tuples.append([si,ei])
|
3949
|
+
# e = Edge.ByVertices([sv,ev])
|
3950
|
+
# d = Dictionary.ByKeysValues(["IFC_global_id", "IFC_name", "IFC_type"], [ifc_rel.id(), ifc_rel.Name, ifc_rel.is_a()])
|
3951
|
+
# e = Topology.SetDictionary(e, d)
|
3952
|
+
# edges.append(e)
|
3953
|
+
# return edges
|
3954
|
+
|
3955
|
+
# ifc_types = IFCObjectTypes(file)
|
3956
|
+
# ifc_objects = IFCObjects(file, include=includeTypes, exclude=excludeTypes)
|
3957
|
+
# vertices = []
|
3958
|
+
# for ifc_object in ifc_objects:
|
3959
|
+
# v = vertexByIFCObject(ifc_object, ifc_types)
|
3960
|
+
# if v:
|
3961
|
+
# vertices.append(v)
|
3962
|
+
# if len(vertices) > 0:
|
3963
|
+
# ifc_relationships = IFCRelationships(file, include=includeRels, exclude=excludeRels)
|
3964
|
+
# edges = edgesByIFCRelationships(ifc_relationships, ifc_types, vertices)
|
3965
|
+
# g = Graph.ByVerticesEdges(vertices, edges)
|
3966
|
+
# else:
|
3967
|
+
# g = None
|
3968
|
+
# return g
|
3675
3969
|
|
3676
3970
|
@staticmethod
|
3677
3971
|
def ByIFCPath(path,
|
@@ -6007,6 +6301,8 @@ class Graph:
|
|
6007
6301
|
return graph
|
6008
6302
|
|
6009
6303
|
|
6304
|
+
|
6305
|
+
|
6010
6306
|
@staticmethod
|
6011
6307
|
def ClosenessCentrality(
|
6012
6308
|
graph,
|
@@ -6021,144 +6317,396 @@ class Graph:
|
|
6021
6317
|
silent: bool = False
|
6022
6318
|
):
|
6023
6319
|
"""
|
6024
|
-
|
6025
|
-
|
6026
|
-
|
6027
|
-
|
6028
|
-
|
6029
|
-
|
6030
|
-
graph : topologic_core.Graph
|
6031
|
-
The input graph.
|
6032
|
-
weightKey : str , optional
|
6033
|
-
If specified, this edge attribute will be used as the distance weight when
|
6034
|
-
computing shortest paths. If set to a name containing "Length" or "Distance",
|
6035
|
-
it will be mapped to "length".
|
6036
|
-
Note: Graph.NetworkXGraph automatically provides a "length" attribute on all edges.
|
6037
|
-
normalize : bool , optional
|
6038
|
-
If True, the returned values are rescaled to [0, 1]. Otherwise raw values
|
6039
|
-
from NetworkX (optionally using the improved formula) are returned.
|
6040
|
-
nxCompatible : bool , optional
|
6041
|
-
If True, use NetworkX's wf_improved scaling (Wasserman and Faust).
|
6042
|
-
For single-component graphs it matches the original formula.
|
6043
|
-
key : str , optional
|
6044
|
-
The dictionary key under which to store the closeness centrality score.
|
6045
|
-
colorKey : str , optional
|
6046
|
-
The dictionary key under which to store a color derived from the score.
|
6047
|
-
colorScale : str , optional
|
6048
|
-
Plotly color scale name (e.g., "viridis", "plasma").
|
6049
|
-
mantissa : int , optional
|
6050
|
-
The number of decimal places to round the result to. Default is 6.
|
6051
|
-
tolerance : float , optional
|
6052
|
-
The desired tolerance. Default is 0.0001.
|
6053
|
-
silent : bool , optional
|
6054
|
-
If set to True, error and warning messages are suppressed. Default is False.
|
6055
|
-
|
6056
|
-
Returns
|
6057
|
-
-------
|
6058
|
-
list[float]
|
6059
|
-
Closeness centrality values for vertices in the same order as Graph.Vertices(graph).
|
6320
|
+
Optimized closeness centrality:
|
6321
|
+
- Avoids NetworkX and costly per-vertex Topologic calls.
|
6322
|
+
- Builds integer-index adjacency once from edges (undirected).
|
6323
|
+
- Unweighted: multi-source BFS (one per node).
|
6324
|
+
- Weighted: Dijkstra per node (heapq), or SciPy csgraph if available.
|
6325
|
+
- Supports 'wf_improved' scaling (nxCompatible) and optional normalization.
|
6060
6326
|
"""
|
6061
|
-
import
|
6062
|
-
|
6063
|
-
import networkx as nx
|
6064
|
-
except Exception as e:
|
6065
|
-
warnings.warn(
|
6066
|
-
f"Graph.ClosenessCentrality - Error: networkx is required but not installed ({e}). Returning None."
|
6067
|
-
)
|
6068
|
-
return None
|
6327
|
+
from collections import deque
|
6328
|
+
import math
|
6069
6329
|
|
6330
|
+
from topologicpy.Topology import Topology
|
6070
6331
|
from topologicpy.Dictionary import Dictionary
|
6071
6332
|
from topologicpy.Color import Color
|
6072
|
-
from topologicpy.Topology import Topology
|
6073
6333
|
from topologicpy.Helper import Helper
|
6334
|
+
from topologicpy.Vertex import Vertex
|
6335
|
+
from topologicpy.Edge import Edge
|
6336
|
+
# NOTE: We are inside Graph.*, so Graph.<...> methods are available.
|
6074
6337
|
|
6075
|
-
#
|
6338
|
+
# Validate graph
|
6076
6339
|
if not Topology.IsInstance(graph, "graph"):
|
6077
6340
|
if not silent:
|
6078
6341
|
print("Graph.ClosenessCentrality - Error: The input is not a valid Graph. Returning None.")
|
6079
6342
|
return None
|
6343
|
+
|
6080
6344
|
vertices = Graph.Vertices(graph)
|
6081
|
-
|
6345
|
+
n = len(vertices)
|
6346
|
+
if n == 0:
|
6082
6347
|
if not silent:
|
6083
6348
|
print("Graph.ClosenessCentrality - Warning: Graph has no vertices. Returning [].")
|
6084
6349
|
return []
|
6085
6350
|
|
6086
|
-
#
|
6351
|
+
# Stable vertex key (prefer an 'id' in the vertex dictionary; else rounded coords)
|
6352
|
+
def vkey(v, r=9):
|
6353
|
+
d = Topology.Dictionary(v)
|
6354
|
+
vid = Dictionary.ValueAtKey(d, "id")
|
6355
|
+
if vid is not None:
|
6356
|
+
return ("id", vid)
|
6357
|
+
return ("xyz", round(Vertex.X(v), r), round(Vertex.Y(v), r), round(Vertex.Z(v), r))
|
6358
|
+
|
6359
|
+
idx_of = {vkey(v): i for i, v in enumerate(vertices)}
|
6360
|
+
|
6361
|
+
# Normalize weight key
|
6087
6362
|
distance_attr = None
|
6088
6363
|
if isinstance(weightKey, str) and weightKey:
|
6089
|
-
|
6364
|
+
wl = weightKey.lower()
|
6365
|
+
if ("len" in wl) or ("dis" in wl):
|
6090
6366
|
weightKey = "length"
|
6091
|
-
distance_attr = weightKey
|
6092
|
-
|
6093
|
-
# Build the NX graph
|
6094
|
-
nx_graph = Graph.NetworkXGraph(graph)
|
6095
|
-
|
6096
|
-
# Graph.NetworkXGraph automatically adds "length" to all edges.
|
6097
|
-
# So if distance_attr == "length", we trust it and skip per-edge checks.
|
6098
|
-
if distance_attr and distance_attr != "length":
|
6099
|
-
# For any non-"length" custom attribute, verify presence; else fall back unweighted.
|
6100
|
-
attr_missing = any(
|
6101
|
-
(distance_attr not in data) or (data[distance_attr] is None)
|
6102
|
-
for _, _, data in nx_graph.edges(data=True)
|
6103
|
-
)
|
6104
|
-
if attr_missing:
|
6105
|
-
if not silent:
|
6106
|
-
print("Graph.ClosenessCentrality - Warning: The specified edge attribute was not found on all edges. Falling back to unweighted closeness.")
|
6107
|
-
distance_arg = None
|
6108
|
-
else:
|
6109
|
-
distance_arg = distance_attr
|
6110
|
-
else:
|
6111
|
-
# Use "length" directly or unweighted if distance_attr is falsy.
|
6112
|
-
distance_arg = distance_attr if distance_attr else None
|
6367
|
+
distance_attr = weightKey # may be "length" or a custom key
|
6113
6368
|
|
6114
|
-
#
|
6115
|
-
|
6116
|
-
|
6117
|
-
|
6118
|
-
if not silent:
|
6119
|
-
print(f"Graph.ClosenessCentrality - Error: NetworkX failed to compute centrality ({e}). Returning None.")
|
6120
|
-
return None
|
6369
|
+
# Build undirected adjacency with minimal weights per edge
|
6370
|
+
# Use dict-of-dict to collapse multi-edges to minimal weight
|
6371
|
+
adj = [dict() for _ in range(n)] # adj[i][j] = weight
|
6372
|
+
edges = Graph.Edges(graph)
|
6121
6373
|
|
6122
|
-
|
6123
|
-
|
6124
|
-
|
6374
|
+
def edge_weight(e):
|
6375
|
+
if distance_attr == "length":
|
6376
|
+
try:
|
6377
|
+
return float(Edge.Length(e))
|
6378
|
+
except Exception:
|
6379
|
+
return 1.0
|
6380
|
+
elif distance_attr:
|
6381
|
+
try:
|
6382
|
+
d = Topology.Dictionary(e)
|
6383
|
+
w = Dictionary.ValueAtKey(d, distance_attr)
|
6384
|
+
return float(w) if (w is not None) else 1.0
|
6385
|
+
except Exception:
|
6386
|
+
return 1.0
|
6387
|
+
else:
|
6388
|
+
return 1.0
|
6389
|
+
|
6390
|
+
for e in edges:
|
6125
6391
|
try:
|
6126
|
-
|
6392
|
+
u = Edge.StartVertex(e)
|
6393
|
+
v = Edge.EndVertex(e)
|
6127
6394
|
except Exception:
|
6128
|
-
|
6129
|
-
|
6130
|
-
|
6395
|
+
# Fallback in odd cases
|
6396
|
+
continue
|
6397
|
+
iu = idx_of.get(vkey(u))
|
6398
|
+
iv = idx_of.get(vkey(v))
|
6399
|
+
if iu is None or iv is None or iu == iv:
|
6400
|
+
continue
|
6401
|
+
w = edge_weight(e)
|
6402
|
+
# Keep minimal weight if duplicates
|
6403
|
+
prev = adj[iu].get(iv)
|
6404
|
+
if (prev is None) or (w < prev):
|
6405
|
+
adj[iu][iv] = w
|
6406
|
+
adj[iv][iu] = w
|
6407
|
+
|
6408
|
+
# Detect weighted vs unweighted
|
6409
|
+
weighted = False
|
6410
|
+
for i in range(n):
|
6411
|
+
if any(abs(w - 1.0) > 1e-12 for w in adj[i].values()):
|
6412
|
+
weighted = True
|
6413
|
+
break
|
6131
6414
|
|
6132
|
-
|
6133
|
-
|
6415
|
+
INF = float("inf")
|
6416
|
+
|
6417
|
+
# ---- shortest paths helpers ----
|
6418
|
+
def bfs_sum(i):
|
6419
|
+
"""Sum of unweighted shortest path distances from i; returns (tot, reachable)."""
|
6420
|
+
dist = [-1] * n
|
6421
|
+
q = deque([i])
|
6422
|
+
dist[i] = 0
|
6423
|
+
reachable = 1
|
6424
|
+
tot = 0
|
6425
|
+
pop = q.popleft; push = q.append
|
6426
|
+
while q:
|
6427
|
+
u = pop()
|
6428
|
+
du = dist[u]
|
6429
|
+
for v in adj[u].keys():
|
6430
|
+
if dist[v] == -1:
|
6431
|
+
dist[v] = du + 1
|
6432
|
+
reachable += 1
|
6433
|
+
tot += dist[v]
|
6434
|
+
push(v)
|
6435
|
+
return float(tot), reachable
|
6436
|
+
|
6437
|
+
def dijkstra_sum(i):
|
6438
|
+
"""Sum of weighted shortest path distances from i; returns (tot, reachable)."""
|
6439
|
+
import heapq
|
6440
|
+
dist = [INF] * n
|
6441
|
+
dist[i] = 0.0
|
6442
|
+
hq = [(0.0, i)]
|
6443
|
+
push = heapq.heappush; pop = heapq.heappop
|
6444
|
+
while hq:
|
6445
|
+
du, u = pop(hq)
|
6446
|
+
if du > dist[u]:
|
6447
|
+
continue
|
6448
|
+
for v, w in adj[u].items():
|
6449
|
+
nd = du + w
|
6450
|
+
if nd < dist[v]:
|
6451
|
+
dist[v] = nd
|
6452
|
+
push(hq, (nd, v))
|
6453
|
+
# Exclude self (0.0) and unreachable (INF)
|
6454
|
+
reachable = 0
|
6455
|
+
tot = 0.0
|
6456
|
+
for d in dist:
|
6457
|
+
if d < INF:
|
6458
|
+
reachable += 1
|
6459
|
+
tot += d
|
6460
|
+
# subtract self-distance
|
6461
|
+
tot -= 0.0
|
6462
|
+
return float(tot), reachable
|
6463
|
+
|
6464
|
+
# SciPy acceleration if weighted and available
|
6465
|
+
use_scipy = False
|
6466
|
+
if weighted:
|
6467
|
+
try:
|
6468
|
+
import numpy as np
|
6469
|
+
from scipy.sparse import csr_matrix
|
6470
|
+
from scipy.sparse.csgraph import dijkstra as sp_dijkstra
|
6471
|
+
use_scipy = True
|
6472
|
+
# Build CSR once
|
6473
|
+
rows, cols, data = [], [], []
|
6474
|
+
for i in range(n):
|
6475
|
+
for j, w in adj[i].items():
|
6476
|
+
rows.append(i); cols.append(j); data.append(float(w))
|
6477
|
+
if len(data) == 0:
|
6478
|
+
use_scipy = False # empty graph; fall back
|
6479
|
+
else:
|
6480
|
+
A = csr_matrix((np.array(data), (np.array(rows), np.array(cols))), shape=(n, n))
|
6481
|
+
except Exception:
|
6482
|
+
use_scipy = False
|
6134
6483
|
|
6135
|
-
#
|
6136
|
-
|
6484
|
+
# ---- centrality computation ----
|
6485
|
+
values = [0.0] * n
|
6486
|
+
if n == 1:
|
6487
|
+
values[0] = 0.0
|
6488
|
+
else:
|
6489
|
+
if not weighted:
|
6490
|
+
for i in range(n):
|
6491
|
+
tot, reachable = bfs_sum(i)
|
6492
|
+
s = max(reachable - 1, 0)
|
6493
|
+
if tot > 0.0:
|
6494
|
+
if nxCompatible:
|
6495
|
+
# Wasserman–Faust improved scaling for disconnected graphs
|
6496
|
+
values[i] = (s / (n - 1)) * (s / tot)
|
6497
|
+
else:
|
6498
|
+
values[i] = s / tot
|
6499
|
+
else:
|
6500
|
+
values[i] = 0.0
|
6501
|
+
else:
|
6502
|
+
if use_scipy:
|
6503
|
+
# All-pairs from SciPy (fast)
|
6504
|
+
import numpy as np
|
6505
|
+
D = sp_dijkstra(A, directed=False, return_predecessors=False)
|
6506
|
+
for i in range(n):
|
6507
|
+
di = D[i]
|
6508
|
+
finite = di[np.isfinite(di)]
|
6509
|
+
# di includes self at 0; reachable count is len(finite)
|
6510
|
+
reachable = int(finite.size)
|
6511
|
+
s = max(reachable - 1, 0)
|
6512
|
+
tot = float(finite.sum()) # includes self=0
|
6513
|
+
if s > 0:
|
6514
|
+
if nxCompatible:
|
6515
|
+
values[i] = (s / (n - 1)) * (s / tot)
|
6516
|
+
else:
|
6517
|
+
values[i] = s / tot
|
6518
|
+
else:
|
6519
|
+
values[i] = 0.0
|
6520
|
+
else:
|
6521
|
+
# Per-source Dijkstra
|
6522
|
+
for i in range(n):
|
6523
|
+
tot, reachable = dijkstra_sum(i)
|
6524
|
+
s = max(reachable - 1, 0)
|
6525
|
+
if tot > 0.0:
|
6526
|
+
if nxCompatible:
|
6527
|
+
values[i] = (s / (n - 1)) * (s / tot)
|
6528
|
+
else:
|
6529
|
+
values[i] = s / tot
|
6530
|
+
else:
|
6531
|
+
values[i] = 0.0
|
6137
6532
|
|
6138
|
-
#
|
6533
|
+
# Optional normalization, round once
|
6534
|
+
out_vals = Helper.Normalize(values) if normalize else values
|
6139
6535
|
if mantissa is not None and mantissa >= 0:
|
6140
|
-
|
6536
|
+
out_vals = [round(v, mantissa) for v in out_vals]
|
6141
6537
|
|
6142
|
-
#
|
6143
|
-
if
|
6144
|
-
|
6145
|
-
max_value = max(color_values)
|
6538
|
+
# Color mapping range (use displayed numbers)
|
6539
|
+
if out_vals:
|
6540
|
+
min_v, max_v = min(out_vals), max(out_vals)
|
6146
6541
|
else:
|
6147
|
-
|
6542
|
+
min_v, max_v = 0.0, 1.0
|
6543
|
+
if abs(max_v - min_v) < tolerance:
|
6544
|
+
max_v = min_v + tolerance
|
6148
6545
|
|
6149
|
-
|
6150
|
-
|
6151
|
-
|
6152
|
-
# Annotate vertices with score and color
|
6153
|
-
for i, value in enumerate(color_values):
|
6546
|
+
# Annotate vertices
|
6547
|
+
for i, value in enumerate(out_vals):
|
6154
6548
|
d = Topology.Dictionary(vertices[i])
|
6155
6549
|
color_hex = Color.AnyToHex(
|
6156
|
-
Color.ByValueInRange(value, minValue=
|
6550
|
+
Color.ByValueInRange(value, minValue=min_v, maxValue=max_v, colorScale=colorScale)
|
6157
6551
|
)
|
6158
|
-
d = Dictionary.SetValuesAtKeys(d, [key, colorKey], [
|
6552
|
+
d = Dictionary.SetValuesAtKeys(d, [key, colorKey], [value, color_hex])
|
6159
6553
|
vertices[i] = Topology.SetDictionary(vertices[i], d)
|
6160
6554
|
|
6161
|
-
return
|
6555
|
+
return out_vals
|
6556
|
+
|
6557
|
+
|
6558
|
+
# @staticmethod
|
6559
|
+
# def ClosenessCentrality_old(
|
6560
|
+
# graph,
|
6561
|
+
# weightKey: str = "length",
|
6562
|
+
# normalize: bool = False,
|
6563
|
+
# nxCompatible: bool = True,
|
6564
|
+
# key: str = "closeness_centrality",
|
6565
|
+
# colorKey: str = "cc_color",
|
6566
|
+
# colorScale: str = "viridis",
|
6567
|
+
# mantissa: int = 6,
|
6568
|
+
# tolerance: float = 0.0001,
|
6569
|
+
# silent: bool = False
|
6570
|
+
# ):
|
6571
|
+
# """
|
6572
|
+
# Returns the closeness centrality of the input graph. The order of the returned
|
6573
|
+
# list matches the order of Graph.Vertices(graph).
|
6574
|
+
# See: https://en.wikipedia.org/wiki/Closeness_centrality
|
6575
|
+
|
6576
|
+
# Parameters
|
6577
|
+
# ----------
|
6578
|
+
# graph : topologic_core.Graph
|
6579
|
+
# The input graph.
|
6580
|
+
# weightKey : str , optional
|
6581
|
+
# If specified, this edge attribute will be used as the distance weight when
|
6582
|
+
# computing shortest paths. If set to a name containing "Length" or "Distance",
|
6583
|
+
# it will be mapped to "length".
|
6584
|
+
# Note: Graph.NetworkXGraph automatically provides a "length" attribute on all edges.
|
6585
|
+
# normalize : bool , optional
|
6586
|
+
# If True, the returned values are rescaled to [0, 1]. Otherwise raw values
|
6587
|
+
# from NetworkX (optionally using the improved formula) are returned.
|
6588
|
+
# nxCompatible : bool , optional
|
6589
|
+
# If True, use NetworkX's wf_improved scaling (Wasserman and Faust).
|
6590
|
+
# For single-component graphs it matches the original formula.
|
6591
|
+
# key : str , optional
|
6592
|
+
# The dictionary key under which to store the closeness centrality score.
|
6593
|
+
# colorKey : str , optional
|
6594
|
+
# The dictionary key under which to store a color derived from the score.
|
6595
|
+
# colorScale : str , optional
|
6596
|
+
# Plotly color scale name (e.g., "viridis", "plasma").
|
6597
|
+
# mantissa : int , optional
|
6598
|
+
# The number of decimal places to round the result to. Default is 6.
|
6599
|
+
# tolerance : float , optional
|
6600
|
+
# The desired tolerance. Default is 0.0001.
|
6601
|
+
# silent : bool , optional
|
6602
|
+
# If set to True, error and warning messages are suppressed. Default is False.
|
6603
|
+
|
6604
|
+
# Returns
|
6605
|
+
# -------
|
6606
|
+
# list[float]
|
6607
|
+
# Closeness centrality values for vertices in the same order as Graph.Vertices(graph).
|
6608
|
+
# """
|
6609
|
+
# import warnings
|
6610
|
+
# try:
|
6611
|
+
# import networkx as nx
|
6612
|
+
# except Exception as e:
|
6613
|
+
# warnings.warn(
|
6614
|
+
# f"Graph.ClosenessCentrality - Error: networkx is required but not installed ({e}). Returning None."
|
6615
|
+
# )
|
6616
|
+
# return None
|
6617
|
+
|
6618
|
+
# from topologicpy.Dictionary import Dictionary
|
6619
|
+
# from topologicpy.Color import Color
|
6620
|
+
# from topologicpy.Topology import Topology
|
6621
|
+
# from topologicpy.Helper import Helper
|
6622
|
+
|
6623
|
+
# # Topology.IsInstance is case-insensitive, so a single call is sufficient.
|
6624
|
+
# if not Topology.IsInstance(graph, "graph"):
|
6625
|
+
# if not silent:
|
6626
|
+
# print("Graph.ClosenessCentrality - Error: The input is not a valid Graph. Returning None.")
|
6627
|
+
# return None
|
6628
|
+
# vertices = Graph.Vertices(graph)
|
6629
|
+
# if len(vertices) == 0:
|
6630
|
+
# if not silent:
|
6631
|
+
# print("Graph.ClosenessCentrality - Warning: Graph has no vertices. Returning [].")
|
6632
|
+
# return []
|
6633
|
+
|
6634
|
+
# # Normalize the weight key semantics
|
6635
|
+
# distance_attr = None
|
6636
|
+
# if isinstance(weightKey, str) and weightKey:
|
6637
|
+
# if ("len" in weightKey.lower()) or ("dis" in weightKey.lower()):
|
6638
|
+
# weightKey = "length"
|
6639
|
+
# distance_attr = weightKey
|
6640
|
+
|
6641
|
+
# # Build the NX graph
|
6642
|
+
# nx_graph = Graph.NetworkXGraph(graph)
|
6643
|
+
|
6644
|
+
# # Graph.NetworkXGraph automatically adds "length" to all edges.
|
6645
|
+
# # So if distance_attr == "length", we trust it and skip per-edge checks.
|
6646
|
+
# if distance_attr and distance_attr != "length":
|
6647
|
+
# # For any non-"length" custom attribute, verify presence; else fall back unweighted.
|
6648
|
+
# attr_missing = any(
|
6649
|
+
# (distance_attr not in data) or (data[distance_attr] is None)
|
6650
|
+
# for _, _, data in nx_graph.edges(data=True)
|
6651
|
+
# )
|
6652
|
+
# if attr_missing:
|
6653
|
+
# if not silent:
|
6654
|
+
# print("Graph.ClosenessCentrality - Warning: The specified edge attribute was not found on all edges. Falling back to unweighted closeness.")
|
6655
|
+
# distance_arg = None
|
6656
|
+
# else:
|
6657
|
+
# distance_arg = distance_attr
|
6658
|
+
# else:
|
6659
|
+
# # Use "length" directly or unweighted if distance_attr is falsy.
|
6660
|
+
# distance_arg = distance_attr if distance_attr else None
|
6661
|
+
|
6662
|
+
# # Compute centrality (dict keyed by NetworkX nodes)
|
6663
|
+
# try:
|
6664
|
+
# cc_dict = nx.closeness_centrality(nx_graph, distance=distance_arg, wf_improved=nxCompatible)
|
6665
|
+
# except Exception as e:
|
6666
|
+
# if not silent:
|
6667
|
+
# print(f"Graph.ClosenessCentrality - Error: NetworkX failed to compute centrality ({e}). Returning None.")
|
6668
|
+
# return None
|
6669
|
+
|
6670
|
+
# # NetworkX vertex ids are in the same numerice order as the list of vertices starting from 0.
|
6671
|
+
# raw_values = []
|
6672
|
+
# for i, v in enumerate(vertices):
|
6673
|
+
# try:
|
6674
|
+
# raw_values.append(float(cc_dict.get(i, 0.0)))
|
6675
|
+
# except Exception:
|
6676
|
+
# if not silent:
|
6677
|
+
# print(f,"Graph.ClosenessCentrality - Warning: Could not retrieve score for vertex {i}. Assigning a Zero (0).")
|
6678
|
+
# raw_values.append(0.0)
|
6679
|
+
|
6680
|
+
# # Optional normalization ONLY once, then rounding once at the end
|
6681
|
+
# values_for_return = Helper.Normalize(raw_values) if normalize else raw_values
|
6682
|
+
|
6683
|
+
# # Values for color scaling should reflect the displayed numbers
|
6684
|
+
# color_values = values_for_return
|
6685
|
+
|
6686
|
+
# # Single rounding at the end for return values
|
6687
|
+
# if mantissa is not None and mantissa >= 0:
|
6688
|
+
# values_for_return = [round(v, mantissa) for v in values_for_return]
|
6689
|
+
|
6690
|
+
# # Prepare color mapping range, guarding equal-range case
|
6691
|
+
# if color_values:
|
6692
|
+
# min_value = min(color_values)
|
6693
|
+
# max_value = max(color_values)
|
6694
|
+
# else:
|
6695
|
+
# min_value, max_value = 0.0, 1.0
|
6696
|
+
|
6697
|
+
# if abs(max_value - min_value) < tolerance:
|
6698
|
+
# max_value = min_value + tolerance
|
6699
|
+
|
6700
|
+
# # Annotate vertices with score and color
|
6701
|
+
# for i, value in enumerate(color_values):
|
6702
|
+
# d = Topology.Dictionary(vertices[i])
|
6703
|
+
# color_hex = Color.AnyToHex(
|
6704
|
+
# Color.ByValueInRange(value, minValue=min_value, maxValue=max_value, colorScale=colorScale)
|
6705
|
+
# )
|
6706
|
+
# d = Dictionary.SetValuesAtKeys(d, [key, colorKey], [values_for_return[i], color_hex])
|
6707
|
+
# vertices[i] = Topology.SetDictionary(vertices[i], d)
|
6708
|
+
|
6709
|
+
# return values_for_return
|
6162
6710
|
|
6163
6711
|
@staticmethod
|
6164
6712
|
def Community(graph, key: str = "partition", mantissa: int = 6, tolerance: float = 0.0001, silent: bool = False):
|
@@ -12962,90 +13510,201 @@ class Graph:
|
|
12962
13510
|
return outgoing_vertices
|
12963
13511
|
|
12964
13512
|
@staticmethod
|
12965
|
-
def PageRank(
|
13513
|
+
def PageRank(
|
13514
|
+
graph,
|
13515
|
+
alpha: float = 0.85,
|
13516
|
+
maxIterations: int = 100,
|
13517
|
+
normalize: bool = True,
|
13518
|
+
directed: bool = False,
|
13519
|
+
key: str = "page_rank",
|
13520
|
+
colorKey: str = "pr_color",
|
13521
|
+
colorScale: str = "viridis",
|
13522
|
+
mantissa: int = 6,
|
13523
|
+
tolerance: float = 1e-4
|
13524
|
+
):
|
12966
13525
|
"""
|
12967
|
-
|
12968
|
-
|
12969
|
-
Parameters
|
12970
|
-
----------
|
12971
|
-
graph : topologic_core.Graph
|
12972
|
-
The input graph.
|
12973
|
-
alpha : float , optional
|
12974
|
-
The damping (dampening) factor. Default is 0.85. See https://en.wikipedia.org/wiki/PageRank.
|
12975
|
-
maxIterations : int , optional
|
12976
|
-
The maximum number of iterations to calculate the page rank. Default is 100.
|
12977
|
-
normalize : bool , optional
|
12978
|
-
If set to True, the results will be normalized from 0 to 1. Otherwise, they won't be. Default is True.
|
12979
|
-
directed : bool , optional
|
12980
|
-
If set to True, the graph is considered as a directed graph. Otherwise, it will be considered as an undirected graph. Default is False.
|
12981
|
-
key : str , optional
|
12982
|
-
The dictionary key under which to store the page_rank score. Default is "page_rank"
|
12983
|
-
colorKey : str , optional
|
12984
|
-
The desired dictionary key under which to store the pagerank color. Default is "pr_color".
|
12985
|
-
colorScale : str , optional
|
12986
|
-
The desired type of plotly color scales to use (e.g. "viridis", "plasma"). Default is "viridis". For a full list of names, see https://plotly.com/python/builtin-colorscales/.
|
12987
|
-
In addition to these, three color-blind friendly scales are included. These are "protanopia", "deuteranopia", and "tritanopia" for red, green, and blue colorblindness respectively.
|
12988
|
-
mantissa : int , optional
|
12989
|
-
The desired length of the mantissa.
|
12990
|
-
tolerance : float , optional
|
12991
|
-
The desired tolerance. Default is 0.0001.
|
12992
|
-
|
12993
|
-
Returns
|
12994
|
-
-------
|
12995
|
-
list
|
12996
|
-
The list of page ranks for the vertices in the graph.
|
13526
|
+
PageRank with stable vertex mapping (by coordinates) so neighbors resolve correctly.
|
13527
|
+
Handles dangling nodes; uses cached neighbor lists and L1 convergence.
|
12997
13528
|
"""
|
12998
13529
|
from topologicpy.Vertex import Vertex
|
12999
13530
|
from topologicpy.Helper import Helper
|
13000
13531
|
from topologicpy.Dictionary import Dictionary
|
13001
13532
|
from topologicpy.Topology import Topology
|
13002
13533
|
from topologicpy.Color import Color
|
13534
|
+
from topologicpy.Graph import Graph
|
13003
13535
|
|
13004
13536
|
vertices = Graph.Vertices(graph)
|
13005
|
-
|
13006
|
-
if
|
13537
|
+
n = len(vertices)
|
13538
|
+
if n < 1:
|
13007
13539
|
print("Graph.PageRank - Error: The input graph parameter has no vertices. Returning None")
|
13008
13540
|
return None
|
13009
|
-
|
13010
|
-
|
13541
|
+
|
13542
|
+
# ---- stable vertex key (coord-based) ----
|
13543
|
+
# Use a modest rounding to be robust to tiny numerical noise.
|
13544
|
+
# If your graphs can have distinct vertices at the exact same coords,
|
13545
|
+
# switch to a stronger key (e.g., include a unique ID from the vertex dictionary).
|
13546
|
+
def vkey(v, r=9):
|
13547
|
+
return (round(Vertex.X(v), r), round(Vertex.Y(v), r), round(Vertex.Z(v), r))
|
13548
|
+
|
13549
|
+
idx_of = {vkey(v): i for i, v in enumerate(vertices)}
|
13550
|
+
|
13551
|
+
# Helper that resolves an arbitrary Topologic vertex to our index
|
13552
|
+
def to_idx(u):
|
13553
|
+
return idx_of.get(vkey(u), None)
|
13554
|
+
|
13555
|
+
# ---- build neighbor lists ONCE (by indices) ----
|
13556
|
+
if directed:
|
13557
|
+
in_neighbors = [[] for _ in range(n)]
|
13558
|
+
out_neighbors = [[] for _ in range(n)]
|
13559
|
+
|
13560
|
+
for i, v in enumerate(vertices):
|
13561
|
+
inv = Graph.IncomingVertices(graph, v, directed=True)
|
13562
|
+
onv = Graph.OutgoingVertices(graph, v, directed=True)
|
13563
|
+
# map to indices, drop misses
|
13564
|
+
in_neighbors[i] = [j for u in inv if (j := to_idx(u)) is not None]
|
13565
|
+
out_neighbors[i] = [j for u in onv if (j := to_idx(u)) is not None]
|
13566
|
+
else:
|
13567
|
+
in_neighbors = [[] for _ in range(n)]
|
13568
|
+
out_neighbors = in_neighbors # same list objects is fine; we set both below
|
13569
|
+
for i, v in enumerate(vertices):
|
13570
|
+
nbrs = Graph.AdjacentVertices(graph, v)
|
13571
|
+
idxs = [j for u in nbrs if (j := to_idx(u)) is not None]
|
13572
|
+
in_neighbors[i] = idxs
|
13573
|
+
out_neighbors = in_neighbors # undirected: in == out
|
13574
|
+
|
13575
|
+
out_degree = [len(out_neighbors[i]) for i in range(n)]
|
13576
|
+
dangling = [i for i in range(n) if out_degree[i] == 0]
|
13577
|
+
|
13578
|
+
# ---- power iteration ----
|
13579
|
+
pr = [1.0 / n] * n
|
13580
|
+
base = (1.0 - alpha) / n
|
13581
|
+
|
13011
13582
|
for _ in range(maxIterations):
|
13012
|
-
|
13013
|
-
for i
|
13014
|
-
|
13015
|
-
|
13016
|
-
|
13017
|
-
|
13018
|
-
|
13019
|
-
|
13020
|
-
|
13021
|
-
|
13022
|
-
|
13023
|
-
|
13583
|
+
# Distribute dangling mass uniformly
|
13584
|
+
dangling_mass = alpha * (sum(pr[i] for i in dangling) / n) if dangling else 0.0
|
13585
|
+
|
13586
|
+
new_pr = [base + dangling_mass] * n
|
13587
|
+
|
13588
|
+
# Sum contributions from incoming neighbors j: alpha * pr[j] / out_degree[j]
|
13589
|
+
for i in range(n):
|
13590
|
+
acc = 0.0
|
13591
|
+
for j in in_neighbors[i]:
|
13592
|
+
deg = out_degree[j]
|
13593
|
+
if deg > 0:
|
13594
|
+
acc += pr[j] / deg
|
13595
|
+
new_pr[i] += alpha * acc
|
13596
|
+
|
13597
|
+
# L1 convergence
|
13598
|
+
if sum(abs(new_pr[i] - pr[i]) for i in range(n)) <= tolerance:
|
13599
|
+
pr = new_pr
|
13024
13600
|
break
|
13601
|
+
pr = new_pr
|
13025
13602
|
|
13026
|
-
|
13027
|
-
if normalize
|
13028
|
-
|
13029
|
-
|
13030
|
-
|
13031
|
-
|
13032
|
-
min_value = 0
|
13033
|
-
max_value = 1
|
13603
|
+
# ---- normalize & write dictionaries ----
|
13604
|
+
if normalize:
|
13605
|
+
pr = Helper.Normalize(pr)
|
13606
|
+
if mantissa > 0:
|
13607
|
+
pr = [round(v, mantissa) for v in pr]
|
13608
|
+
min_v, max_v = 0.0, 1.0
|
13034
13609
|
else:
|
13035
|
-
|
13036
|
-
max_value = max(values)
|
13610
|
+
min_v, max_v = (min(pr), max(pr)) if n > 0 else (0.0, 0.0)
|
13037
13611
|
|
13038
|
-
for i, value in enumerate(
|
13612
|
+
for i, value in enumerate(pr):
|
13039
13613
|
d = Topology.Dictionary(vertices[i])
|
13040
|
-
color = Color.AnyToHex(
|
13614
|
+
color = Color.AnyToHex(
|
13615
|
+
Color.ByValueInRange(value, minValue=min_v, maxValue=max_v, colorScale=colorScale)
|
13616
|
+
)
|
13041
13617
|
d = Dictionary.SetValuesAtKeys(d, [key, colorKey], [value, color])
|
13042
13618
|
vertices[i] = Topology.SetDictionary(vertices[i], d)
|
13043
|
-
|
13044
|
-
|
13045
|
-
|
13046
|
-
|
13047
|
-
|
13048
|
-
|
13619
|
+
|
13620
|
+
return pr
|
13621
|
+
|
13622
|
+
|
13623
|
+
# @staticmethod
|
13624
|
+
# def PageRank_old(graph, alpha: float = 0.85, maxIterations: int = 100, normalize: bool = True, directed: bool = False, key: str = "page_rank", colorKey="pr_color", colorScale="viridis", mantissa: int = 6, tolerance: float = 0.0001):
|
13625
|
+
# """
|
13626
|
+
# Calculates PageRank scores for vertices in a directed graph. see https://en.wikipedia.org/wiki/PageRank.
|
13627
|
+
|
13628
|
+
# Parameters
|
13629
|
+
# ----------
|
13630
|
+
# graph : topologic_core.Graph
|
13631
|
+
# The input graph.
|
13632
|
+
# alpha : float , optional
|
13633
|
+
# The damping (dampening) factor. Default is 0.85. See https://en.wikipedia.org/wiki/PageRank.
|
13634
|
+
# maxIterations : int , optional
|
13635
|
+
# The maximum number of iterations to calculate the page rank. Default is 100.
|
13636
|
+
# normalize : bool , optional
|
13637
|
+
# If set to True, the results will be normalized from 0 to 1. Otherwise, they won't be. Default is True.
|
13638
|
+
# directed : bool , optional
|
13639
|
+
# If set to True, the graph is considered as a directed graph. Otherwise, it will be considered as an undirected graph. Default is False.
|
13640
|
+
# key : str , optional
|
13641
|
+
# The dictionary key under which to store the page_rank score. Default is "page_rank"
|
13642
|
+
# colorKey : str , optional
|
13643
|
+
# The desired dictionary key under which to store the pagerank color. Default is "pr_color".
|
13644
|
+
# colorScale : str , optional
|
13645
|
+
# The desired type of plotly color scales to use (e.g. "viridis", "plasma"). Default is "viridis". For a full list of names, see https://plotly.com/python/builtin-colorscales/.
|
13646
|
+
# In addition to these, three color-blind friendly scales are included. These are "protanopia", "deuteranopia", and "tritanopia" for red, green, and blue colorblindness respectively.
|
13647
|
+
# mantissa : int , optional
|
13648
|
+
# The desired length of the mantissa.
|
13649
|
+
# tolerance : float , optional
|
13650
|
+
# The desired tolerance. Default is 0.0001.
|
13651
|
+
|
13652
|
+
# Returns
|
13653
|
+
# -------
|
13654
|
+
# list
|
13655
|
+
# The list of page ranks for the vertices in the graph.
|
13656
|
+
# """
|
13657
|
+
# from topologicpy.Vertex import Vertex
|
13658
|
+
# from topologicpy.Helper import Helper
|
13659
|
+
# from topologicpy.Dictionary import Dictionary
|
13660
|
+
# from topologicpy.Topology import Topology
|
13661
|
+
# from topologicpy.Color import Color
|
13662
|
+
|
13663
|
+
# vertices = Graph.Vertices(graph)
|
13664
|
+
# num_vertices = len(vertices)
|
13665
|
+
# if num_vertices < 1:
|
13666
|
+
# print("Graph.PageRank - Error: The input graph parameter has no vertices. Returning None")
|
13667
|
+
# return None
|
13668
|
+
# initial_score = 1.0 / num_vertices
|
13669
|
+
# values = [initial_score for vertex in vertices]
|
13670
|
+
# for _ in range(maxIterations):
|
13671
|
+
# new_scores = [0 for vertex in vertices]
|
13672
|
+
# for i, vertex in enumerate(vertices):
|
13673
|
+
# incoming_score = 0
|
13674
|
+
# for incoming_vertex in Graph.IncomingVertices(graph, vertex, directed=directed):
|
13675
|
+
# if len(Graph.IncomingVertices(graph, incoming_vertex, directed=directed)) > 0:
|
13676
|
+
# vi = Vertex.Index(incoming_vertex, vertices, tolerance=tolerance)
|
13677
|
+
# if not vi == None:
|
13678
|
+
# incoming_score += values[vi] / len(Graph.IncomingVertices(graph, incoming_vertex, directed=directed))
|
13679
|
+
# new_scores[i] = alpha * incoming_score + (1 - alpha) / num_vertices
|
13680
|
+
|
13681
|
+
# # Check for convergence
|
13682
|
+
# if all(abs(new_scores[i] - values[i]) <= tolerance for i in range(len(vertices))):
|
13683
|
+
# break
|
13684
|
+
|
13685
|
+
# values = new_scores
|
13686
|
+
# if normalize == True:
|
13687
|
+
# if mantissa > 0: # We cannot round numbers from 0 to 1 with a mantissa = 0.
|
13688
|
+
# values = [round(v, mantissa) for v in Helper.Normalize(values)]
|
13689
|
+
# else:
|
13690
|
+
# values = Helper.Normalize(values)
|
13691
|
+
# min_value = 0
|
13692
|
+
# max_value = 1
|
13693
|
+
# else:
|
13694
|
+
# min_value = min(values)
|
13695
|
+
# max_value = max(values)
|
13696
|
+
|
13697
|
+
# for i, value in enumerate(values):
|
13698
|
+
# d = Topology.Dictionary(vertices[i])
|
13699
|
+
# color = Color.AnyToHex(Color.ByValueInRange(value, minValue=min_value, maxValue=max_value, colorScale=colorScale))
|
13700
|
+
# d = Dictionary.SetValuesAtKeys(d, [key, colorKey], [value, color])
|
13701
|
+
# vertices[i] = Topology.SetDictionary(vertices[i], d)
|
13702
|
+
|
13703
|
+
# for i, v in enumerate(vertices):
|
13704
|
+
# d = Topology.Dictionary(v)
|
13705
|
+
# d = Dictionary.SetValueAtKey(d, key, values[i])
|
13706
|
+
# v = Topology.SetDictionary(v, d)
|
13707
|
+
# return values
|
13049
13708
|
|
13050
13709
|
@staticmethod
|
13051
13710
|
def Partition(graph, method: str = "Betweenness", n: int = 2, m: int = 10, key: str ="partition",
|