topologicpy 0.8.31__py3-none-any.whl → 0.8.33__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.
@@ -48,6 +48,99 @@ except:
48
48
  warnings.warn("CellComplex - Error: Could not import scipy.")
49
49
 
50
50
  class CellComplex():
51
+ @staticmethod
52
+ def AdjacencyDictionary(cellComplex, cellLabelKey: str = None, faceKey: str = None, includeWeights: bool = False, reverse: bool = False, mantissa: int = 6, silent: bool = False):
53
+ """
54
+ Returns the adjacency dictionary of the input Graph.
55
+
56
+ Parameters
57
+ ----------
58
+ cellComplex : topologic_core.CellComplex
59
+ The input cellComplex.
60
+ cellLabelKey : str , optional
61
+ The returned cells are labelled according to the dictionary values stored under this key.
62
+ If the cellLabelKey does not exist, it will be created and the cells are labelled numerically and stored in the vertex dictionary under this key. The default is None.
63
+ faceKey : str , optional
64
+ If set, the faces' dictionaries will be searched for this key to set their weight. If the key is set to "Area" (case insensitive), the area of the shared faces will be used as its weight. If set to None, a weight of 1 will be used. The default is None.
65
+ includeWeights : bool , optional
66
+ If set to True, edge weights are included. Otherwise, they are not. The default is False.
67
+ reverse : bool , optional
68
+ If set to True, the cells are sorted in reverse order (only if cellLabelKey is set). Otherwise, they are not. The default is False.
69
+
70
+ silent : bool , optional
71
+ If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
72
+
73
+ Returns
74
+ -------
75
+ dict
76
+ The adjacency dictionary.
77
+ """
78
+ from topologicpy.Face import Face
79
+ from topologicpy.Dictionary import Dictionary
80
+ from topologicpy.Topology import Topology
81
+ from topologicpy.Helper import Helper
82
+
83
+ if not Topology.IsInstance(cellComplex, "CellComplex"):
84
+ if not silent:
85
+ print("CellComplex.AdjacencyDictionary - Error: The input cellComplex input parameter is not a valid cellComplex. Returning None.")
86
+ return None
87
+ if cellLabelKey == None:
88
+ cellLabelKey = "__label__"
89
+ if not isinstance(cellLabelKey, str):
90
+ if not silent:
91
+ print("CellComplex.AdjacencyDictionary - Error: The input cellLabelKey is not a valid string. Returning None.")
92
+ return None
93
+ all_cells = Topology.Cells(cellComplex)
94
+ labels = []
95
+ n = max(len(str(len(all_cells))), 3)
96
+ for i, cell in enumerate(all_cells):
97
+ d = Topology.Dictionary(cell)
98
+ value = Dictionary.ValueAtKey(d, cellLabelKey)
99
+ if value == None:
100
+ value = str(i+1).zfill(n)
101
+ if d == None:
102
+ d = Dictionary.ByKeyValue(cellLabelKey, value)
103
+ else:
104
+ d = Dictionary.SetValueAtKey(d, cellLabelKey, value)
105
+ cell = Topology.SetDictionary(cell, d)
106
+ labels.append(value)
107
+ all_cells = Helper.Sort(all_cells, labels)
108
+ labels.sort()
109
+ order = len(all_cells)
110
+ adjDict = {}
111
+ for i in range(order):
112
+ cell = all_cells[i]
113
+ cell_label = labels[i]
114
+ adjCells = Topology.AdjacentTopologies(cell, hostTopology=cellComplex, topologyType="cell")
115
+ temp_list = []
116
+ for adjCell in adjCells:
117
+ adj_label = Dictionary.ValueAtKey(Topology.Dictionary(adjCell), cellLabelKey)
118
+ adj_index = labels.index(adj_label)
119
+ if includeWeights == True:
120
+ if faceKey == None:
121
+ weight = 1
122
+ elif "area" in faceKey.lower():
123
+ shared_topologies = Topology.SharedTopologies(cell, adjCell)
124
+ faces = shared_topologies.get("faces", [])
125
+ weight = sum([Face.Area(face, mantissa=mantissa) for face in faces])
126
+ else:
127
+ shared_topologies = Topology.SharedTopologies(cell, adjCell)
128
+ faces = shared_topologies.get("faces", [])
129
+ weight = sum([Dictionary.ValueAtKey(Topology.Dictionary(face),faceKey, 0) for face in faces])
130
+ if not adj_index == None:
131
+ temp_list.append((adj_label, weight))
132
+ else:
133
+ if not adj_index == None:
134
+ temp_list.append(adj_label)
135
+ temp_list.sort()
136
+ adjDict[cell_label] = temp_list
137
+ if cellLabelKey == "__label__": # This is label we added, so remove it
138
+ for cell in all_cells:
139
+ d = Topology.Dictionary(cell)
140
+ d = Dictionary.RemoveKey(d, cellLabelKey)
141
+ cell = Topology.SetDictionary(cell, d)
142
+ return adjDict
143
+
51
144
  @staticmethod
52
145
  def Box(origin= None,
53
146
  width: float = 1.0, length: float = 1.0, height: float = 1.0,
@@ -159,7 +252,7 @@ class CellComplex():
159
252
  if transferDictionaries == True:
160
253
  for temp_cell in temp_cells:
161
254
  v = Topology.InternalVertex(temp_cell, tolerance=tolerance)
162
- enclosing_cells = Vertex.EnclosingCell(v, cluster)
255
+ enclosing_cells = Vertex.EnclosingCells(v, cluster)
163
256
  dictionaries = [Topology.Dictionary(ec) for ec in enclosing_cells]
164
257
  d = Dictionary.ByMergedDictionaries(dictionaries, silent=silent)
165
258
  temp_cell = Topology.SetDictionary(temp_cell, d)
@@ -1050,6 +1143,125 @@ class CellComplex():
1050
1143
  shells = Topology.Shells(cellComplex)
1051
1144
  return shells
1052
1145
 
1146
+
1147
+ def _grow_connected_group(seed_idx, group_size, adjacency, visited_global):
1148
+ """
1149
+ Attempts to grow a group of the given size starting from seed_idx using adjacency.
1150
+ Returns a list of indices if successful, else None.
1151
+ """
1152
+ from collections import deque
1153
+ import random
1154
+
1155
+ group = [seed_idx]
1156
+ visited = set(group)
1157
+ queue = deque([seed_idx])
1158
+
1159
+ while queue and len(group) < group_size:
1160
+ current = queue.popleft()
1161
+ neighbors = adjacency.get(current, [])
1162
+ random.shuffle(neighbors)
1163
+ for neighbor in neighbors:
1164
+ if neighbor not in visited and neighbor not in visited_global:
1165
+ group.append(neighbor)
1166
+ visited.add(neighbor)
1167
+ queue.append(neighbor)
1168
+ if len(group) >= group_size:
1169
+ break
1170
+
1171
+ return group if len(group) == group_size else None
1172
+
1173
+ def SubCombinations(cellComplex,
1174
+ minCells: int = 2,
1175
+ maxCells: int = None,
1176
+ maxCombinations: int = 100,
1177
+ timeLimit: int = 10,
1178
+ silent: bool = False):
1179
+ """
1180
+ Creates sub-combination cellComplexes of the input cellComplex. Warning: This is prone to combinatorial explosion.
1181
+
1182
+ Parameters
1183
+ ----------
1184
+ cellComplex : topologic_core.cellComplex
1185
+ The input cellComplex
1186
+ minCells : int , optional
1187
+ The minimum number of cells to include in a combination. The default is 2.
1188
+ maxCells : int , optional
1189
+ The maximum number of cells to include in a combinations. The default is None which means the maximum will be set to the number of cells in the cellComplex minus 1.
1190
+ maxCombinations : int , optional
1191
+ The maximum number of combinations to create. The default is 100.
1192
+ timeLimit : int , optional
1193
+ The time limit in seconds. The default is 10 seconds. Note that this time limit only applies to creating the combination indices and not the actual CellComplexes.
1194
+ tolerance : float , optional
1195
+ The tolerance for computing if the input vertex is external to the input topology. The default is 0.0001.
1196
+ silent : bool , optional
1197
+ If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
1198
+
1199
+ Returns
1200
+ -------
1201
+ list
1202
+ The list of created CellComplex sub-combinations.
1203
+
1204
+ """
1205
+ from topologicpy.Cluster import Cluster
1206
+ from topologicpy.Topology import Topology
1207
+ from topologicpy.Dictionary import Dictionary
1208
+ import random
1209
+ import time
1210
+
1211
+ if not Topology.IsInstance(cellComplex, "CellComplex"):
1212
+ if not silent:
1213
+ print("CellComplex.SubCombinations - Error: The cellComplex input parameter is not a valid cellComplex. Returning None.")
1214
+ return None
1215
+
1216
+
1217
+ start_time = time.time()
1218
+ all_cells = CellComplex.Cells(cellComplex)
1219
+ num_cells = len(all_cells)
1220
+ indices = list(range(num_cells))
1221
+ cell_label_key = "index"
1222
+ # 1. Assign a unique index to each cell's dictionary
1223
+ all_cells = [Topology.SetDictionary(cell, Dictionary.ByKeyValue(cell_label_key, i)) for i, cell in enumerate(all_cells)]
1224
+ if maxCells == None:
1225
+ maxCells = len(all_cells) - 1
1226
+ # 2. Allocate counts per group size
1227
+ group_sizes = list(range(minCells, maxCells + 1))
1228
+ combinations_per_size = maxCombinations // len(group_sizes)
1229
+
1230
+ # 3. Build adjacency dict
1231
+ adjacency = CellComplex.AdjacencyDictionary(cellComplex, cellLabelKey=cell_label_key, faceKey=None, includeWeights=False)
1232
+
1233
+ results = []
1234
+ seen_groups = set() # sets of sorted indices
1235
+
1236
+ # 4. Start from longest group size
1237
+ for group_size in reversed(group_sizes):
1238
+ remaining = combinations_per_size
1239
+ tries = 0
1240
+ max_tries = combinations_per_size * 10 # fallback guard
1241
+ while remaining > 0 and time.time() - start_time < timeLimit and tries < max_tries:
1242
+ random.shuffle(indices)
1243
+ for seed in indices:
1244
+ if time.time() - start_time > timeLimit:
1245
+ break
1246
+ group = CellComplex._grow_connected_group(seed, group_size, adjacency, visited_global=seen_groups)
1247
+ if group:
1248
+ key = tuple(sorted(group))
1249
+ if key not in seen_groups:
1250
+ tries += 1
1251
+ seen_groups.add(key)
1252
+ results.append(group)
1253
+ remaining -= 1
1254
+ if remaining <= 0 or tries >= max_tries:
1255
+ break
1256
+ # 5. Build CellComplex SubCombinations
1257
+ return_combinations = []
1258
+ for i, result in enumerate(reversed(results)):
1259
+ cells = [all_cells[i] for i in result]
1260
+ combination = Topology.SelfMerge(Cluster.ByTopologies(cells))
1261
+ if Topology.IsInstance(combination, "CellComplex"):
1262
+ return_combinations.append(combination)
1263
+ return return_combinations
1264
+
1053
1265
  @staticmethod
1054
1266
  def Tetrahedron(origin = None, length: float = 1, depth: int = 1, direction=[0,0,1], placement="center", mantissa: int = 6, tolerance: float = 0.0001, silent: bool = False):
1055
1267
  """
topologicpy/Edge.py CHANGED
@@ -306,29 +306,45 @@ class Edge():
306
306
  """
307
307
  from topologicpy.Vertex import Vertex
308
308
  from topologicpy.Topology import Topology
309
+ import inspect
309
310
 
310
311
  edge = None
311
312
  if not Topology.IsInstance(vertexA, "Vertex"):
312
313
  if not silent:
313
314
  print("Edge.ByStartVertexEndVertex - Error: The input vertexA parameter is not a valid topologic vertex. Returning None.")
315
+ curframe = inspect.currentframe()
316
+ calframe = inspect.getouterframes(curframe, 2)
317
+ print('caller name:', calframe[1][3])
314
318
  return None
315
319
  if not Topology.IsInstance(vertexB, "Vertex"):
316
320
  if not silent:
317
321
  print("Edge.ByStartVertexEndVertex - Error: The input vertexB parameter is not a valid topologic vertex. Returning None.")
322
+ curframe = inspect.currentframe()
323
+ calframe = inspect.getouterframes(curframe, 2)
324
+ print('caller name:', calframe[1][3])
318
325
  return None
319
326
  if Topology.IsSame(vertexA, vertexB):
320
327
  if not silent:
321
328
  print("Edge.ByStartVertexEndVertex - Error: The input vertexA and vertexB parameters are the same vertex. Returning None.")
329
+ curframe = inspect.currentframe()
330
+ calframe = inspect.getouterframes(curframe, 2)
331
+ print('caller name:', calframe[1][3])
322
332
  return None
323
333
  if Vertex.Distance(vertexA, vertexB) <= tolerance:
324
334
  if not silent:
325
335
  print("Edge.ByStartVertexEndVertex - Error: The distance between the input vertexA and vertexB parameters is less than the input tolerance. Returning None.")
336
+ curframe = inspect.currentframe()
337
+ calframe = inspect.getouterframes(curframe, 2)
338
+ print('caller name:', calframe[1][3])
326
339
  return None
327
340
  try:
328
341
  edge = topologic.Edge.ByStartVertexEndVertex(vertexA, vertexB) # Hook to Core
329
342
  except:
330
343
  if not silent:
331
344
  print("Edge.ByStartVertexEndVertex - Error: Could not create an edge. Returning None.")
345
+ curframe = inspect.currentframe()
346
+ calframe = inspect.getouterframes(curframe, 2)
347
+ print('caller name:', calframe[1][3])
332
348
  edge = None
333
349
  return edge
334
350