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/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
- showVertexLabels=False,
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
- showEdgeLabels=False,
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
- showVertexLabels : bool , optional
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
- showEdgeLabels : bool , optional
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, showVertexLabels=showVertexLabels, showVertexLegend=showVertexLegend, edgeColor=edgeColor, edgeWidth=edgeWidth, edgeLabelKey=edgeLabelKey, edgeGroupKey=edgeGroupKey, edgeGroups=edgeGroups, showEdges=showEdges, showEdgeLabels=showEdgeLabels, showEdgeLegend=showEdgeLegend, colorScale=colorScale, silent=silent)
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
  """