topologicpy 0.7.71__py3-none-any.whl → 0.7.73__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/BVH.py +9 -7
- topologicpy/CellComplex.py +92 -23
- topologicpy/Cluster.py +1 -1
- topologicpy/Color.py +67 -1
- topologicpy/Dictionary.py +108 -10
- topologicpy/Edge.py +8 -4
- topologicpy/Face.py +1 -1
- topologicpy/Graph.py +114 -6
- topologicpy/Helper.py +111 -0
- topologicpy/Plotly.py +505 -344
- topologicpy/Shell.py +24 -24
- topologicpy/Topology.py +234 -117
- topologicpy/Wire.py +50 -56
- topologicpy/version.py +1 -1
- {topologicpy-0.7.71.dist-info → topologicpy-0.7.73.dist-info}/METADATA +1 -1
- topologicpy-0.7.73.dist-info/RECORD +36 -0
- {topologicpy-0.7.71.dist-info → topologicpy-0.7.73.dist-info}/WHEEL +1 -1
- topologicpy-0.7.71.dist-info/RECORD +0 -36
- {topologicpy-0.7.71.dist-info → topologicpy-0.7.73.dist-info}/LICENSE +0 -0
- {topologicpy-0.7.71.dist-info → topologicpy-0.7.73.dist-info}/top_level.txt +0 -0
topologicpy/Graph.py
CHANGED
@@ -742,6 +742,102 @@ class Graph:
|
|
742
742
|
_ = graph.AllPaths(vertexA, vertexB, True, timeLimit, paths) # Hook to Core
|
743
743
|
return paths
|
744
744
|
|
745
|
+
@staticmethod
|
746
|
+
def AreIsomorphic(graphA, graphB, maxIterations=10, silent=False):
|
747
|
+
"""
|
748
|
+
Tests if the two input graphs are isomorphic according to the Weisfeiler Lehman graph isomorphism test. See https://en.wikipedia.org/wiki/Weisfeiler_Leman_graph_isomorphism_test
|
749
|
+
|
750
|
+
Parameters
|
751
|
+
----------
|
752
|
+
graphA : topologic_core.Graph
|
753
|
+
The first input graph.
|
754
|
+
graphB : topologic_core.Graph
|
755
|
+
The second input graph.
|
756
|
+
maxIterations : int , optional
|
757
|
+
This number limits the number of iterations to prevent the function from running indefinitely, particularly for very large or complex graphs.
|
758
|
+
silent : bool , optional
|
759
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
760
|
+
|
761
|
+
Returns
|
762
|
+
-------
|
763
|
+
bool
|
764
|
+
True if the two input graphs are isomorphic. False otherwise
|
765
|
+
|
766
|
+
"""
|
767
|
+
|
768
|
+
from topologicpy.Topology import Topology
|
769
|
+
|
770
|
+
def weisfeiler_lehman_test(graph1, graph2, max_iterations=10):
|
771
|
+
"""
|
772
|
+
Test if two graphs are isomorphic using the Weisfeiler-Leman (WL) algorithm with early stopping.
|
773
|
+
|
774
|
+
Parameters:
|
775
|
+
graph1 (dict): Adjacency list representation of the first graph.
|
776
|
+
graph2 (dict): Adjacency list representation of the second graph.
|
777
|
+
max_iterations (int): Maximum WL iterations allowed (default is 10).
|
778
|
+
|
779
|
+
Returns:
|
780
|
+
bool: True if the graphs are WL-isomorphic, False otherwise.
|
781
|
+
"""
|
782
|
+
|
783
|
+
def wl_iteration(labels, graph):
|
784
|
+
"""Perform one WL iteration and return updated labels."""
|
785
|
+
new_labels = {}
|
786
|
+
for node in graph:
|
787
|
+
neighborhood_labels = sorted([labels[neighbor] for neighbor in graph[node]])
|
788
|
+
new_labels[node] = (labels[node], tuple(neighborhood_labels))
|
789
|
+
unique_labels = {}
|
790
|
+
count = 0
|
791
|
+
for node in sorted(new_labels):
|
792
|
+
if new_labels[node] not in unique_labels:
|
793
|
+
unique_labels[new_labels[node]] = count
|
794
|
+
count += 1
|
795
|
+
new_labels[node] = unique_labels[new_labels[node]]
|
796
|
+
return new_labels
|
797
|
+
|
798
|
+
# Initialize labels
|
799
|
+
labels1 = {node: 1 for node in graph1}
|
800
|
+
labels2 = {node: 1 for node in graph2}
|
801
|
+
|
802
|
+
for i in range(max_iterations):
|
803
|
+
# Perform WL iteration for both graphs
|
804
|
+
new_labels1 = wl_iteration(labels1, graph1)
|
805
|
+
new_labels2 = wl_iteration(labels2, graph2)
|
806
|
+
|
807
|
+
# Check if the label distributions match
|
808
|
+
if sorted(new_labels1.values()) != sorted(new_labels2.values()):
|
809
|
+
return False
|
810
|
+
|
811
|
+
# Check for stability (early stopping)
|
812
|
+
if new_labels1 == labels1 and new_labels2 == labels2:
|
813
|
+
break
|
814
|
+
|
815
|
+
# Update labels for next iteration
|
816
|
+
labels1, labels2 = new_labels1, new_labels2
|
817
|
+
|
818
|
+
return True
|
819
|
+
|
820
|
+
if not Topology.IsInstance(graphA, "Graph") and not Topology.IsInstance(graphB, "Graph"):
|
821
|
+
if not silent:
|
822
|
+
print("Graph.AreIsomorphic - Error: The input graph parameters are not valid graphs. Returning None.")
|
823
|
+
return None
|
824
|
+
if not Topology.IsInstance(graphA, "Graph"):
|
825
|
+
if not silent:
|
826
|
+
print("Graph.AreIsomorphic - Error: The input graphA parameter is not a valid graph. Returning None.")
|
827
|
+
return None
|
828
|
+
if not Topology.IsInstance(graphB, "Graph"):
|
829
|
+
if not silent:
|
830
|
+
print("Graph.AreIsomorphic - Error: The input graphB parameter is not a valid graph. Returning None.")
|
831
|
+
return None
|
832
|
+
if maxIterations <= 0:
|
833
|
+
if not silent:
|
834
|
+
print("Graph.AreIsomorphic - Error: The input maxIterations parameter is not within a valid range. Returning None.")
|
835
|
+
return None
|
836
|
+
|
837
|
+
g1 = Graph.AdjacencyDictionary(graphA)
|
838
|
+
g2 = Graph.AdjacencyDictionary(graphB)
|
839
|
+
return weisfeiler_lehman_test(g1, g2, max_iterations=maxIterations)
|
840
|
+
|
745
841
|
@staticmethod
|
746
842
|
def AverageClusteringCoefficient(graph, mantissa: int = 6, silent: bool = False):
|
747
843
|
"""
|
@@ -3971,7 +4067,7 @@ class Graph:
|
|
3971
4067
|
d = Graph.TopologicalDistance(graph, va, vb, tolerance)
|
3972
4068
|
top_dist += d
|
3973
4069
|
if top_dist == 0:
|
3974
|
-
print("Topological Distance is Zero
|
4070
|
+
print("Graph.ClosenessCentrality - Warning: Topological Distance is Zero.")
|
3975
4071
|
scores.append(0)
|
3976
4072
|
else:
|
3977
4073
|
scores.append(round((n-1)/top_dist, mantissa))
|
@@ -7847,20 +7943,24 @@ class Graph:
|
|
7847
7943
|
sides = 8,
|
7848
7944
|
angle = 0,
|
7849
7945
|
vertexColor="black",
|
7946
|
+
vertexColorKey=None,
|
7850
7947
|
vertexSize=6,
|
7948
|
+
vertexSizeKey=None,
|
7851
7949
|
vertexLabelKey=None,
|
7852
7950
|
vertexGroupKey=None,
|
7853
7951
|
vertexGroups=[],
|
7854
7952
|
showVertices=True,
|
7855
|
-
|
7953
|
+
showVertexLabel=False,
|
7856
7954
|
showVertexLegend=False,
|
7857
7955
|
edgeColor="black",
|
7956
|
+
edgeColorKey=None,
|
7858
7957
|
edgeWidth=1,
|
7958
|
+
edgeWidthKey=None,
|
7859
7959
|
edgeLabelKey=None,
|
7860
7960
|
edgeGroupKey=None,
|
7861
7961
|
edgeGroups=[],
|
7862
7962
|
showEdges=True,
|
7863
|
-
|
7963
|
+
showEdgeLabel=False,
|
7864
7964
|
showEdgeLegend=False,
|
7865
7965
|
colorScale='viridis',
|
7866
7966
|
renderer=None,
|
@@ -7904,8 +8004,12 @@ class Graph:
|
|
7904
8004
|
- An hsv/hsva string (e.g. 'hsv(0,100%,100%)')
|
7905
8005
|
- A named CSS color.
|
7906
8006
|
The default is "black".
|
8007
|
+
vertexColorKey : str , optional
|
8008
|
+
The dictionary key under which to find the vertex color. The default is None.
|
7907
8009
|
vertexSize : float , optional
|
7908
8010
|
The desired size of the vertices. The default is 1.1.
|
8011
|
+
vertexSizeKey : str , optional
|
8012
|
+
The dictionary key under which to find the vertex size. The default is None.
|
7909
8013
|
vertexLabelKey : str , optional
|
7910
8014
|
The dictionary key to use to display the vertex label. The default is None.
|
7911
8015
|
vertexGroupKey : str , optional
|
@@ -7914,7 +8018,7 @@ class Graph:
|
|
7914
8018
|
The list of vertex groups against which to index the color of the vertex. The default is [].
|
7915
8019
|
showVertices : bool , optional
|
7916
8020
|
If set to True the vertices will be drawn. Otherwise, they will not be drawn. The default is True.
|
7917
|
-
|
8021
|
+
showVertexLabel : bool , optional
|
7918
8022
|
If set to True, the vertex labels are shown permenantely on screen. Otherwise, they are not. The default is False.
|
7919
8023
|
showVertexLegend : bool , optional
|
7920
8024
|
If set to True the vertex legend will be drawn. Otherwise, it will not be drawn. The default is False.
|
@@ -7926,8 +8030,12 @@ class Graph:
|
|
7926
8030
|
- An hsv/hsva string (e.g. 'hsv(0,100%,100%)')
|
7927
8031
|
- A named CSS color.
|
7928
8032
|
The default is "black".
|
8033
|
+
edgeColorKey : str , optional
|
8034
|
+
The dictionary key under which to find the edge color. The default is None.
|
7929
8035
|
edgeWidth : float , optional
|
7930
8036
|
The desired thickness of the output edges. The default is 1.
|
8037
|
+
edgeWidthKey : str , optional
|
8038
|
+
The dictionary key under which to find the edge width. The default is None.
|
7931
8039
|
edgeLabelKey : str , optional
|
7932
8040
|
The dictionary key to use to display the edge label. The default is None.
|
7933
8041
|
edgeGroupKey : str , optional
|
@@ -7936,7 +8044,7 @@ class Graph:
|
|
7936
8044
|
The list of edge groups against which to index the color of the edge. The default is [].
|
7937
8045
|
showEdges : bool , optional
|
7938
8046
|
If set to True the edges will be drawn. Otherwise, they will not be drawn. The default is True.
|
7939
|
-
|
8047
|
+
showEdgeLabel : bool , optional
|
7940
8048
|
If set to True, the edge labels are shown permenantely on screen. Otherwise, they are not. The default is False.
|
7941
8049
|
showEdgeLegend : bool , optional
|
7942
8050
|
If set to True the edge legend will be drawn. Otherwise, it will not be drawn. The default is False.
|
@@ -7997,7 +8105,7 @@ class Graph:
|
|
7997
8105
|
print("Graph.Show - Error: The input graph is not a valid graph. Returning None.")
|
7998
8106
|
return None
|
7999
8107
|
|
8000
|
-
data= Plotly.DataByGraph(graph, sagitta=sagitta, absolute=absolute, sides=sides, angle=angle, vertexColor=vertexColor, vertexSize=vertexSize, vertexLabelKey=vertexLabelKey, vertexGroupKey=vertexGroupKey, vertexGroups=vertexGroups, showVertices=showVertices,
|
8108
|
+
data= Plotly.DataByGraph(graph, sagitta=sagitta, absolute=absolute, sides=sides, angle=angle, vertexColor=vertexColor, vertexColorKey=vertexColorKey, vertexSize=vertexSize, vertexSizeKey=vertexSizeKey, vertexLabelKey=vertexLabelKey, vertexGroupKey=vertexGroupKey, vertexGroups=vertexGroups, showVertices=showVertices, showVertexLabel=showVertexLabel, showVertexLegend=showVertexLegend, edgeColor=edgeColor, edgeColorKey=edgeColorKey, edgeWidth=edgeWidth, edgeWidthKey=edgeWidthKey, edgeLabelKey=edgeLabelKey, edgeGroupKey=edgeGroupKey, edgeGroups=edgeGroups, showEdges=showEdges, showEdgeLabel=showEdgeLabel, showEdgeLegend=showEdgeLegend, colorScale=colorScale, silent=silent)
|
8001
8109
|
fig = Plotly.FigureByData(data, width=width, height=height, xAxis=xAxis, yAxis=yAxis, zAxis=zAxis, axisSize=axisSize, backgroundColor=backgroundColor,
|
8002
8110
|
marginLeft=marginLeft, marginRight=marginRight, marginTop=marginTop, marginBottom=marginBottom, tolerance=tolerance)
|
8003
8111
|
Plotly.Show(fig, renderer=renderer, camera=camera, center=center, up=up, projection=projection)
|
topologicpy/Helper.py
CHANGED
@@ -95,6 +95,117 @@ class Helper:
|
|
95
95
|
|
96
96
|
return closest_index
|
97
97
|
|
98
|
+
@staticmethod
|
99
|
+
def ClusterByKeys(elements, dictionaries, *keys, silent=False):
|
100
|
+
"""
|
101
|
+
Clusters the input list of elements and dictionaries based on the input key or keys.
|
102
|
+
|
103
|
+
Parameters
|
104
|
+
----------
|
105
|
+
elements : list
|
106
|
+
The input list of elements to be clustered.
|
107
|
+
dictionaries : list[Topology.Dictionary]
|
108
|
+
The input list of dictionaries to be consulted for clustering. This is assumed to be in the same order as the list of elements.
|
109
|
+
keys : str or list or comma-separated str input parameters
|
110
|
+
The key or keys in the topology's dictionary to use for clustering.
|
111
|
+
silent : bool , optional
|
112
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
113
|
+
|
114
|
+
|
115
|
+
Returns
|
116
|
+
-------
|
117
|
+
dict
|
118
|
+
A dictionary containing the elements and the dictionaries, but clustered. The dictionary has two keys:
|
119
|
+
"elements": list
|
120
|
+
A nested list of elements where each item is a list of elements with the same key values.
|
121
|
+
"dictionaries": list
|
122
|
+
A nested list of dictionaries where each item is a list of dictionaries with the same key values.
|
123
|
+
"""
|
124
|
+
|
125
|
+
from topologicpy.Dictionary import Dictionary
|
126
|
+
from topologicpy.Helper import Helper
|
127
|
+
import inspect
|
128
|
+
|
129
|
+
keys_list = list(keys)
|
130
|
+
keys_list = Helper.Flatten(keys_list)
|
131
|
+
keys_list = [x for x in keys_list if isinstance(x, str)]
|
132
|
+
|
133
|
+
if len(keys_list) == 0:
|
134
|
+
if not silent:
|
135
|
+
print("Helper.ClusterByKeys - Error: The input keys parameter is an empty list. Returning None.")
|
136
|
+
curframe = inspect.currentframe()
|
137
|
+
calframe = inspect.getouterframes(curframe, 2)
|
138
|
+
print('caller name:', calframe[1][3])
|
139
|
+
return None
|
140
|
+
|
141
|
+
if len(keys_list) == 0:
|
142
|
+
if not silent:
|
143
|
+
print("Helper.ClusterByKeys - Error: The input keys parameter does not contain any valid strings. Returning None.")
|
144
|
+
curframe = inspect.currentframe()
|
145
|
+
calframe = inspect.getouterframes(curframe, 2)
|
146
|
+
print('caller name:', calframe[1][3])
|
147
|
+
return None
|
148
|
+
if not (len(elements) == len(dictionaries)):
|
149
|
+
if not silent:
|
150
|
+
print("Helper.ClusterByKeys - Error: The input elements parameter does not have the same length as the input dictionaries parameter. Returning None.")
|
151
|
+
return None
|
152
|
+
|
153
|
+
elements_clusters = []
|
154
|
+
dict_clusters = []
|
155
|
+
values = []
|
156
|
+
new_dictionaries = []
|
157
|
+
for i, d in enumerate(dictionaries):
|
158
|
+
element = elements[i]
|
159
|
+
|
160
|
+
d_keys = Dictionary.Keys(d)
|
161
|
+
if len(d_keys) > 0:
|
162
|
+
values_list = []
|
163
|
+
for key in keys_list:
|
164
|
+
v = Dictionary.ValueAtKey(d, key)
|
165
|
+
if not v == None:
|
166
|
+
values_list.append(v)
|
167
|
+
values_list = str(values_list)
|
168
|
+
d = Dictionary.SetValueAtKey(d, "_clustering_key_", values_list)
|
169
|
+
new_dictionaries.append(d)
|
170
|
+
values.append(values_list)
|
171
|
+
|
172
|
+
values = list(set(values))
|
173
|
+
remaining_dictionaries = [x for x in new_dictionaries]
|
174
|
+
remaining_elements = [x for x in elements]
|
175
|
+
remaining_indices = [i for i, x in enumerate(elements)]
|
176
|
+
|
177
|
+
if len(values) == 0:
|
178
|
+
return {"elements": [elements], "dictionaries": [dictionaries]}
|
179
|
+
for value in values:
|
180
|
+
if len(remaining_dictionaries) == 0:
|
181
|
+
break
|
182
|
+
dict = Dictionary.Filter(remaining_elements, remaining_dictionaries, searchType="equal to", key="_clustering_key_", value=value)
|
183
|
+
filtered_indices = dict['filteredIndices']
|
184
|
+
final_dictionaries = []
|
185
|
+
final_elements = []
|
186
|
+
if len(filtered_indices) > 0:
|
187
|
+
for filtered_index in filtered_indices:
|
188
|
+
filtered_dictionary = remaining_dictionaries[filtered_index]
|
189
|
+
filtered_dictionary = Dictionary.RemoveKey(filtered_dictionary, "_clustering_key_")
|
190
|
+
final_dictionaries.append(filtered_dictionary)
|
191
|
+
filtered_element = remaining_elements[filtered_index]
|
192
|
+
final_elements.append(filtered_element)
|
193
|
+
dict_clusters.append(final_dictionaries)
|
194
|
+
elements_clusters.append(final_elements)
|
195
|
+
remaining_dictionaries = dict['otherDictionaries']
|
196
|
+
remaining_elements = dict['otherElements']
|
197
|
+
remaining_indices = dict['otherIndices']
|
198
|
+
if len(remaining_elements) > 0:
|
199
|
+
temp_dict_cluster = []
|
200
|
+
temp_element_cluster = []
|
201
|
+
for remaining_index in remaining_indices:
|
202
|
+
temp_element_cluster.append(remaining_elements[remaining_index])
|
203
|
+
temp_dict_cluster.append(remaining_dictionaries[remaining_index])
|
204
|
+
if len(temp_element_cluster) > 0:
|
205
|
+
dict_clusters.append(temp_dict_cluster)
|
206
|
+
elements_clusters.append(temp_element_cluster)
|
207
|
+
return {"elements": elements_clusters, "dictionaries": dict_clusters}
|
208
|
+
|
98
209
|
@staticmethod
|
99
210
|
def Flatten(listA):
|
100
211
|
"""
|