topologicpy 0.8.45__py3-none-any.whl → 0.8.46__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 +175 -37
- topologicpy/version.py +1 -1
- {topologicpy-0.8.45.dist-info → topologicpy-0.8.46.dist-info}/METADATA +1 -1
- {topologicpy-0.8.45.dist-info → topologicpy-0.8.46.dist-info}/RECORD +7 -7
- {topologicpy-0.8.45.dist-info → topologicpy-0.8.46.dist-info}/WHEEL +0 -0
- {topologicpy-0.8.45.dist-info → topologicpy-0.8.46.dist-info}/licenses/LICENSE +0 -0
- {topologicpy-0.8.45.dist-info → topologicpy-0.8.46.dist-info}/top_level.txt +0 -0
topologicpy/Graph.py
CHANGED
@@ -4800,7 +4800,8 @@ class Graph:
|
|
4800
4800
|
weightJaccard: float = 0.0,
|
4801
4801
|
vertexIDKey: str = "id",
|
4802
4802
|
edgeWeightKey: str = None,
|
4803
|
-
|
4803
|
+
vertexKey: str = None,
|
4804
|
+
iterations: int = 2,
|
4804
4805
|
mantissa: int = 6,
|
4805
4806
|
silent: bool = False):
|
4806
4807
|
"""
|
@@ -4847,8 +4848,10 @@ class Graph:
|
|
4847
4848
|
The dictionary key under which to find the weight of the edge for weighted graphs.
|
4848
4849
|
If this parameter is specified as "length" or "distance" then the length of the edge is used as its weight.
|
4849
4850
|
The default is None which means all edges are treated as if they have a weight of 1.
|
4851
|
+
vertexKey: str , optional
|
4852
|
+
The vertex key to use for the Weifeiler-Lehman initial labels. The default is None which means it will use vertex degree as an initial label.
|
4850
4853
|
iterations : int , optional
|
4851
|
-
The desired number of Weisfeiler-Lehman iterations. Default is
|
4854
|
+
The desired number of Weisfeiler-Lehman kernel iterations. Default is 2.
|
4852
4855
|
mantissa : int , optional
|
4853
4856
|
The desired length of the mantissa. The default is 6.
|
4854
4857
|
silent : bool , optional
|
@@ -4935,7 +4938,7 @@ class Graph:
|
|
4935
4938
|
If this parameter is specified as "length" or "distance" then the length of the edge is used as its weight.
|
4936
4939
|
The default is None which means all edges are treated as if they have a weight of 1.
|
4937
4940
|
iterations : int , optional
|
4938
|
-
The desired number of Weisfeiler-Lehman iterations. Default is
|
4941
|
+
The desired number of Weisfeiler-Lehman iterations. Default is 2.
|
4939
4942
|
mantissa : int , optional
|
4940
4943
|
The desired length of the mantissa. The default is 6.
|
4941
4944
|
|
@@ -5062,39 +5065,9 @@ class Graph:
|
|
5062
5065
|
|
5063
5066
|
return round((vertex_score + edge_score) / 2, mantissa)
|
5064
5067
|
|
5065
|
-
def
|
5066
|
-
|
5067
|
-
|
5068
|
-
|
5069
|
-
for v in vertices:
|
5070
|
-
d = Topology.Dictionary(v)
|
5071
|
-
label = str(Dictionary.ValueAtKey(d, "label")) if d and Dictionary.ValueAtKey(d, "label") else "0"
|
5072
|
-
labels[v] = label
|
5073
|
-
|
5074
|
-
all_label_counts = Counter()
|
5075
|
-
|
5076
|
-
for _ in range(iterations):
|
5077
|
-
new_labels = {}
|
5078
|
-
for v in vertices:
|
5079
|
-
neighbors = Graph.AdjacentVertices(graph, v)
|
5080
|
-
neighbor_labels = sorted(labels.get(n, "0") for n in neighbors)
|
5081
|
-
long_label = labels[v] + "_" + "_".join(neighbor_labels)
|
5082
|
-
hashed_label = hashlib.md5(long_label.encode()).hexdigest()
|
5083
|
-
new_labels[v] = hashed_label
|
5084
|
-
all_label_counts[hashed_label] += 1
|
5085
|
-
labels = new_labels
|
5086
|
-
|
5087
|
-
return all_label_counts
|
5088
|
-
|
5089
|
-
def weisfeiler_lehman_similarity(graphA, graphB, iterations=3, mantissa=6):
|
5090
|
-
f1 = weisfeiler_lehman_fingerprint(graphA, iterations)
|
5091
|
-
f2 = weisfeiler_lehman_fingerprint(graphB, iterations)
|
5092
|
-
|
5093
|
-
common_labels = set(f1.keys()) & set(f2.keys())
|
5094
|
-
score = sum(min(f1[label], f2[label]) for label in common_labels)
|
5095
|
-
norm = max(sum(f1.values()), sum(f2.values()), 1)
|
5096
|
-
|
5097
|
-
return round(score / norm, mantissa)
|
5068
|
+
def weisfeiler_lehman_similarity(graphA, graphB, key=None, iterations=3, mantissa=6):
|
5069
|
+
score = Graph.WLKernel(graphA, graphB, key=key, iterations=iterations, normalize=True, mantissa=mantissa)
|
5070
|
+
return score
|
5098
5071
|
|
5099
5072
|
if not Topology.IsInstance(graphA, "graph"):
|
5100
5073
|
if not silent:
|
@@ -5130,7 +5103,7 @@ class Graph:
|
|
5130
5103
|
jaccard_score = weighted_jaccard_similarity(graphA, graphB, vertexIDKey=vertexIDKey, edgeWeightKey=edgeWeightKey, mantissa=mantissa) if weightJaccard else 0
|
5131
5104
|
pagerank_score = pagerank_similarity(graphA, graphB, mantissa=mantissa) if weightPageRank else 0
|
5132
5105
|
structure_score = structure_similarity(graphA, graphB, mantissa=mantissa) if weightStructure else 0
|
5133
|
-
weisfeiler_lehman_score = weisfeiler_lehman_similarity(graphA, graphB, iterations, mantissa=mantissa) if weightWeisfeilerLehman else 0
|
5106
|
+
weisfeiler_lehman_score = weisfeiler_lehman_similarity(graphA, graphB, key=vertexKey, iterations=iterations, mantissa=mantissa) if weightWeisfeilerLehman else 0
|
5134
5107
|
|
5135
5108
|
weighted_sum = (
|
5136
5109
|
accessibility_centrality_score * weightAccessibilityCentrality +
|
@@ -13919,6 +13892,171 @@ class Graph:
|
|
13919
13892
|
diffBA = Graph.Difference(graphB, graphA, vertexKeys=vertexKeys, useCentroid=useCentroid, tolerance=tolerance, silent=True)
|
13920
13893
|
return Graph.Union(diffAB, diffBA, vertexKeys=vertexKeys, useCentroid=useCentroid, tolerance=tolerance, silent=True)
|
13921
13894
|
|
13895
|
+
@staticmethod
|
13896
|
+
def WLFeatures(graph, key: str = None, iterations: int = 2, silent: bool = False):
|
13897
|
+
"""
|
13898
|
+
Returns a Weisfeiler-Lehman subtree features for a Graph. See https://en.wikipedia.org/wiki/Weisfeiler_Leman_graph_isomorphism_test
|
13899
|
+
|
13900
|
+
Parameters
|
13901
|
+
----------
|
13902
|
+
graph : topologic_core.Graph
|
13903
|
+
The input graph.
|
13904
|
+
key : str , optional
|
13905
|
+
The vertex key to use as an initial label. The default is None which means the vertex degree is used instead.
|
13906
|
+
iterations : int , optional
|
13907
|
+
The desired number of WL iterations. (non-negative int). The default is 2.
|
13908
|
+
silent : bool, optional
|
13909
|
+
If set to True, error and warning messages are suppressed. Default is False.
|
13910
|
+
|
13911
|
+
Returns
|
13912
|
+
-------
|
13913
|
+
dict
|
13914
|
+
{feature_id: count} where feature_id is an int representing a WL label.
|
13915
|
+
"""
|
13916
|
+
|
13917
|
+
from topologicpy.Topology import Topology
|
13918
|
+
from topologicpy.Dictionary import Dictionary
|
13919
|
+
from collections import defaultdict
|
13920
|
+
|
13921
|
+
def _neighbors_map(graph):
|
13922
|
+
"""
|
13923
|
+
Returns:
|
13924
|
+
vertices: list of vertex objects in a stable order
|
13925
|
+
vidx: dict mapping vertex -> index
|
13926
|
+
nbrs: dict index -> sorted list of neighbor indices
|
13927
|
+
"""
|
13928
|
+
vertices = Graph.Vertices(graph)
|
13929
|
+
vidx = {v: i for i, v in enumerate(vertices)}
|
13930
|
+
nbrs = {}
|
13931
|
+
for v in vertices:
|
13932
|
+
i = vidx[v]
|
13933
|
+
adj = Graph.AdjacentVertices(graph, v) or []
|
13934
|
+
nbrs[i] = sorted(vidx[a] for a in adj if a in vidx and a is not v)
|
13935
|
+
return vertices, vidx, nbrs
|
13936
|
+
|
13937
|
+
def _initial_labels(graph, key=None, default="degree"):
|
13938
|
+
"""
|
13939
|
+
Returns an integer label per node index using either vertex dictionary labels
|
13940
|
+
or a structural default (degree or constant).
|
13941
|
+
"""
|
13942
|
+
vertices, vidx, nbrs = _neighbors_map(graph)
|
13943
|
+
labels = {}
|
13944
|
+
if key:
|
13945
|
+
found_any = False
|
13946
|
+
tmp = {}
|
13947
|
+
for v in vertices:
|
13948
|
+
d = Topology.Dictionary(v)
|
13949
|
+
val = Dictionary.ValueAtKey(d, key)
|
13950
|
+
if val is not None:
|
13951
|
+
found_any = True
|
13952
|
+
tmp[vidx[v]] = str(val) if val is not None else None
|
13953
|
+
if found_any:
|
13954
|
+
# fill missing with a sentinel
|
13955
|
+
for i, val in tmp.items():
|
13956
|
+
labels[i] = val if val is not None else "__MISSING__"
|
13957
|
+
else:
|
13958
|
+
# fall back to structural init if no labels exist
|
13959
|
+
if default == "degree":
|
13960
|
+
labels = {i: str(len(nbrs[i])) for i in nbrs}
|
13961
|
+
else:
|
13962
|
+
labels = {i: "0" for i in nbrs}
|
13963
|
+
else: # Add a vertex degree information.
|
13964
|
+
_ = Graph.DegreeCentrality(graph, key="_dc_")
|
13965
|
+
return _initial_labels(graph, key="_dc_")
|
13966
|
+
return labels, nbrs
|
13967
|
+
|
13968
|
+
def _canonize_string_labels(str_labels):
|
13969
|
+
"""
|
13970
|
+
Deterministically map arbitrary strings to dense integer ids.
|
13971
|
+
Returns:
|
13972
|
+
int_labels: dict node_index -> int label
|
13973
|
+
vocab: dict string_label -> int id
|
13974
|
+
"""
|
13975
|
+
# stable order by string to keep mapping deterministic across runs
|
13976
|
+
unique = sorted(set(str_labels.values()))
|
13977
|
+
vocab = {lab: k for k, lab in enumerate(unique)}
|
13978
|
+
return {i: vocab[s] for i, s in str_labels.items()}, vocab
|
13979
|
+
|
13980
|
+
from topologicpy.Topology import Topology
|
13981
|
+
|
13982
|
+
if not Topology.IsInstance(graph, "Graph"):
|
13983
|
+
if not silent:
|
13984
|
+
print("Graph.WLFeatures - Error: The input graph parameter is not a valid topologic graph. Returning None.")
|
13985
|
+
return None
|
13986
|
+
|
13987
|
+
str_labels, nbrs = _initial_labels(graph, key=key)
|
13988
|
+
features = defaultdict(int)
|
13989
|
+
|
13990
|
+
# iteration 0
|
13991
|
+
labels, _ = _canonize_string_labels(str_labels)
|
13992
|
+
for lab in labels.values():
|
13993
|
+
features[lab] += 1
|
13994
|
+
|
13995
|
+
# WL iterations
|
13996
|
+
cur = labels
|
13997
|
+
for _ in range(iterations):
|
13998
|
+
new_str = {}
|
13999
|
+
for i in nbrs:
|
14000
|
+
neigh = [cur[j] for j in nbrs[i]]
|
14001
|
+
neigh.sort()
|
14002
|
+
new_str[i] = f"{cur[i]}|{','.join(map(str, neigh))}"
|
14003
|
+
cur, _ = _canonize_string_labels(new_str)
|
14004
|
+
for lab in cur.values():
|
14005
|
+
features[lab] += 1
|
14006
|
+
|
14007
|
+
return dict(features)
|
14008
|
+
|
14009
|
+
@staticmethod
|
14010
|
+
def WLKernel(graphA, graphB, key: str = None, iterations: int = 2, normalize: bool = True, mantissa: int = 6, silent: bool = False):
|
14011
|
+
"""
|
14012
|
+
Returns a cosine-normalized Weisfeiler-Lehman kernel between two graphs. See https://en.wikipedia.org/wiki/Weisfeiler_Leman_graph_isomorphism_test
|
14013
|
+
|
14014
|
+
Parameters
|
14015
|
+
----------
|
14016
|
+
graphA : topologic_core.Graph
|
14017
|
+
The first input graph.
|
14018
|
+
graphB : topologic_core.Graph
|
14019
|
+
The second input graph.
|
14020
|
+
key : str , optional
|
14021
|
+
The vertex key to use as an initial label. The default is None which means the vertex degree is used instead.
|
14022
|
+
iterations : int , optional
|
14023
|
+
The desired number of WL iterations. (non-negative int). The default is 2.
|
14024
|
+
normalize : bool , optional
|
14025
|
+
if set to True, the returned value is normalized between 0 and 1. The default is True.
|
14026
|
+
mantissa : int , optional
|
14027
|
+
The desired length of the mantissa. The default is 6.
|
14028
|
+
|
14029
|
+
Returns
|
14030
|
+
-------
|
14031
|
+
float
|
14032
|
+
The cosine-normalized Weisfeiler-Lehman kernel
|
14033
|
+
"""
|
14034
|
+
from topologicpy.Topology import Topology
|
14035
|
+
|
14036
|
+
if not Topology.IsInstance(graphA, "Graph"):
|
14037
|
+
if not silent:
|
14038
|
+
print("Graph.WLFeatures - Error: The input graphA parameter is not a valid topologic graph. Returning None.")
|
14039
|
+
return None
|
14040
|
+
if not Topology.IsInstance(graphB, "Graph"):
|
14041
|
+
if not silent:
|
14042
|
+
print("Graph.WLFeatures - Error: The input graphB parameter is not a valid topologic graph. Returning None.")
|
14043
|
+
return None
|
14044
|
+
f1 = Graph.WLFeatures(graphA, key=key, iterations=iterations)
|
14045
|
+
f2 = Graph.WLFeatures(graphB, key=key, iterations=iterations)
|
14046
|
+
|
14047
|
+
# dot product
|
14048
|
+
keys = set(f1) | set(f2)
|
14049
|
+
dot = sum(f1.get(k, 0) * f2.get(k, 0) for k in keys)
|
14050
|
+
|
14051
|
+
if not normalize:
|
14052
|
+
return round(float(dot), mantissa)
|
14053
|
+
|
14054
|
+
import math
|
14055
|
+
n1 = math.sqrt(sum(v*v for v in f1.values()))
|
14056
|
+
n2 = math.sqrt(sum(v*v for v in f2.values()))
|
14057
|
+
return_value = float(dot) / (n1 * n2) if n1 > 0 and n2 > 0 else 0.0
|
14058
|
+
return round(return_value, mantissa)
|
14059
|
+
|
13922
14060
|
@staticmethod
|
13923
14061
|
def XOR(graphA, graphB, vertexKeys, useCentroid: bool = False, tolerance: float = 0.001, silent: bool = False):
|
13924
14062
|
"""
|
topologicpy/version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = '0.8.
|
1
|
+
__version__ = '0.8.46'
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: topologicpy
|
3
|
-
Version: 0.8.
|
3
|
+
Version: 0.8.46
|
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
|
@@ -12,7 +12,7 @@ topologicpy/Dictionary.py,sha256=sPskW5bopbDzLz6MGKm8lN_OeyeAgsqdLvwwNcG0J3g,446
|
|
12
12
|
topologicpy/Edge.py,sha256=dLoAPuRKbjVg_dzloTgjRnQyv_05U9nfrtLO3tqyuys,74167
|
13
13
|
topologicpy/EnergyModel.py,sha256=Pyb28gDDwhzlQIH0xqAygqS0P3SJxWyyV7OWS_AAfRs,53856
|
14
14
|
topologicpy/Face.py,sha256=pN1fssyDLYWf1vU0NOBRx69DaUL958wRSxT-7VBCuCg,203184
|
15
|
-
topologicpy/Graph.py,sha256=
|
15
|
+
topologicpy/Graph.py,sha256=io5l8IV60q5ySrpPTid7TL0he4ouD9nJIoTU-erL6Z8,661704
|
16
16
|
topologicpy/Grid.py,sha256=EbI2NcYhQDpD5mItd7A1Lpr8Puuf87vZPWuoh7_gChQ,18483
|
17
17
|
topologicpy/Helper.py,sha256=qEsE4yaboEGW94q9lFCff0I_JwwTTQnDAFXw006yHaQ,31203
|
18
18
|
topologicpy/Honeybee.py,sha256=yctkwfdupKnp7bAOjP1Z4YaYpRrWoMEb4gz9Z5zaWwE,21751
|
@@ -30,9 +30,9 @@ topologicpy/Vector.py,sha256=X12eqskn28bdB7sLY1EZhq3noPYzPbNEgHPb4a959ss,42302
|
|
30
30
|
topologicpy/Vertex.py,sha256=RlGQnxQSb_kAus3tJgXd-v-Ptubtt09PQPA9IMwfXmI,84835
|
31
31
|
topologicpy/Wire.py,sha256=sJE8qwqYOomvN3snMWmj2P2-Sq25ul_OQ95YFz6DFUw,230553
|
32
32
|
topologicpy/__init__.py,sha256=RMftibjgAnHB1vdL-muo71RwMS4972JCxHuRHOlU428,928
|
33
|
-
topologicpy/version.py,sha256=
|
34
|
-
topologicpy-0.8.
|
35
|
-
topologicpy-0.8.
|
36
|
-
topologicpy-0.8.
|
37
|
-
topologicpy-0.8.
|
38
|
-
topologicpy-0.8.
|
33
|
+
topologicpy/version.py,sha256=lWn332GY1e40h1ASpBjJjwG1eoQEgys8nvUCuPd_VMU,23
|
34
|
+
topologicpy-0.8.46.dist-info/licenses/LICENSE,sha256=FK0vJ73LuE8PYJAn7LutsReWR47-Ooovw2dnRe5yV6Q,681
|
35
|
+
topologicpy-0.8.46.dist-info/METADATA,sha256=TJ0qyM1tswkGy-aJjUmdi5Zmn1BLH1RAoFV9iEi6EI8,10535
|
36
|
+
topologicpy-0.8.46.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
37
|
+
topologicpy-0.8.46.dist-info/top_level.txt,sha256=J30bDzW92Ob7hw3zA8V34Jlp-vvsfIkGzkr8sqvb4Uw,12
|
38
|
+
topologicpy-0.8.46.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|