topologicpy 0.5.8__py3-none-any.whl → 6.0.0__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.
Files changed (94) hide show
  1. topologicpy/Aperture.py +72 -72
  2. topologicpy/Cell.py +2169 -2169
  3. topologicpy/CellComplex.py +1137 -1137
  4. topologicpy/Cluster.py +1288 -1280
  5. topologicpy/Color.py +423 -393
  6. topologicpy/Context.py +79 -79
  7. topologicpy/DGL.py +3213 -3136
  8. topologicpy/Dictionary.py +698 -695
  9. topologicpy/Edge.py +1187 -1187
  10. topologicpy/EnergyModel.py +1180 -1171
  11. topologicpy/Face.py +2141 -2141
  12. topologicpy/Graph.py +7768 -7700
  13. topologicpy/Grid.py +353 -353
  14. topologicpy/Helper.py +507 -507
  15. topologicpy/Honeybee.py +461 -461
  16. topologicpy/Matrix.py +271 -271
  17. topologicpy/Neo4j.py +521 -521
  18. topologicpy/Plotly.py +2 -2
  19. topologicpy/Polyskel.py +541 -541
  20. topologicpy/Shell.py +1768 -1768
  21. topologicpy/Speckle.py +508 -508
  22. topologicpy/Topology.py +7060 -6988
  23. topologicpy/Vector.py +905 -905
  24. topologicpy/Vertex.py +1585 -1585
  25. topologicpy/Wire.py +3050 -3050
  26. topologicpy/__init__.py +22 -38
  27. topologicpy/version.py +1 -0
  28. {topologicpy-0.5.8.dist-info → topologicpy-6.0.0.dist-info}/LICENSE +661 -704
  29. topologicpy-6.0.0.dist-info/METADATA +751 -0
  30. topologicpy-6.0.0.dist-info/RECORD +32 -0
  31. topologicpy/bin/linux/topologic/__init__.py +0 -2
  32. topologicpy/bin/linux/topologic/libTKBO-6bdf205d.so.7.7.0 +0 -0
  33. topologicpy/bin/linux/topologic/libTKBRep-2960a069.so.7.7.0 +0 -0
  34. topologicpy/bin/linux/topologic/libTKBool-c44b74bd.so.7.7.0 +0 -0
  35. topologicpy/bin/linux/topologic/libTKFillet-9a670ba0.so.7.7.0 +0 -0
  36. topologicpy/bin/linux/topologic/libTKG2d-8f31849e.so.7.7.0 +0 -0
  37. topologicpy/bin/linux/topologic/libTKG3d-4c6bce57.so.7.7.0 +0 -0
  38. topologicpy/bin/linux/topologic/libTKGeomAlgo-26066fd9.so.7.7.0 +0 -0
  39. topologicpy/bin/linux/topologic/libTKGeomBase-2116cabe.so.7.7.0 +0 -0
  40. topologicpy/bin/linux/topologic/libTKMath-72572fa8.so.7.7.0 +0 -0
  41. topologicpy/bin/linux/topologic/libTKMesh-2a060427.so.7.7.0 +0 -0
  42. topologicpy/bin/linux/topologic/libTKOffset-6cab68ff.so.7.7.0 +0 -0
  43. topologicpy/bin/linux/topologic/libTKPrim-eb1262b3.so.7.7.0 +0 -0
  44. topologicpy/bin/linux/topologic/libTKShHealing-e67e5cc7.so.7.7.0 +0 -0
  45. topologicpy/bin/linux/topologic/libTKTopAlgo-e4c96c33.so.7.7.0 +0 -0
  46. topologicpy/bin/linux/topologic/libTKernel-fb7fe3b7.so.7.7.0 +0 -0
  47. topologicpy/bin/linux/topologic/libgcc_s-32c1665e.so.1 +0 -0
  48. topologicpy/bin/linux/topologic/libstdc++-672d7b41.so.6.0.30 +0 -0
  49. topologicpy/bin/linux/topologic/topologic.cpython-310-x86_64-linux-gnu.so +0 -0
  50. topologicpy/bin/linux/topologic/topologic.cpython-311-x86_64-linux-gnu.so +0 -0
  51. topologicpy/bin/linux/topologic/topologic.cpython-38-x86_64-linux-gnu.so +0 -0
  52. topologicpy/bin/linux/topologic/topologic.cpython-39-x86_64-linux-gnu.so +0 -0
  53. topologicpy/bin/linux/topologic.libs/libTKBO-6bdf205d.so.7.7.0 +0 -0
  54. topologicpy/bin/linux/topologic.libs/libTKBRep-2960a069.so.7.7.0 +0 -0
  55. topologicpy/bin/linux/topologic.libs/libTKBool-c44b74bd.so.7.7.0 +0 -0
  56. topologicpy/bin/linux/topologic.libs/libTKFillet-9a670ba0.so.7.7.0 +0 -0
  57. topologicpy/bin/linux/topologic.libs/libTKG2d-8f31849e.so.7.7.0 +0 -0
  58. topologicpy/bin/linux/topologic.libs/libTKG3d-4c6bce57.so.7.7.0 +0 -0
  59. topologicpy/bin/linux/topologic.libs/libTKGeomAlgo-26066fd9.so.7.7.0 +0 -0
  60. topologicpy/bin/linux/topologic.libs/libTKGeomBase-2116cabe.so.7.7.0 +0 -0
  61. topologicpy/bin/linux/topologic.libs/libTKMath-72572fa8.so.7.7.0 +0 -0
  62. topologicpy/bin/linux/topologic.libs/libTKMesh-2a060427.so.7.7.0 +0 -0
  63. topologicpy/bin/linux/topologic.libs/libTKOffset-6cab68ff.so.7.7.0 +0 -0
  64. topologicpy/bin/linux/topologic.libs/libTKPrim-eb1262b3.so.7.7.0 +0 -0
  65. topologicpy/bin/linux/topologic.libs/libTKShHealing-e67e5cc7.so.7.7.0 +0 -0
  66. topologicpy/bin/linux/topologic.libs/libTKTopAlgo-e4c96c33.so.7.7.0 +0 -0
  67. topologicpy/bin/linux/topologic.libs/libTKernel-fb7fe3b7.so.7.7.0 +0 -0
  68. topologicpy/bin/linux/topologic.libs/libgcc_s-32c1665e.so.1 +0 -0
  69. topologicpy/bin/linux/topologic.libs/libstdc++-672d7b41.so.6.0.30 +0 -0
  70. topologicpy/bin/macos/topologic/__init__.py +0 -2
  71. topologicpy/bin/windows/topologic/TKBO-f6b191de.dll +0 -0
  72. topologicpy/bin/windows/topologic/TKBRep-e56a600e.dll +0 -0
  73. topologicpy/bin/windows/topologic/TKBool-7b8d47ae.dll +0 -0
  74. topologicpy/bin/windows/topologic/TKFillet-0ddbf0a8.dll +0 -0
  75. topologicpy/bin/windows/topologic/TKG2d-2e2dee3d.dll +0 -0
  76. topologicpy/bin/windows/topologic/TKG3d-6674513d.dll +0 -0
  77. topologicpy/bin/windows/topologic/TKGeomAlgo-d240e370.dll +0 -0
  78. topologicpy/bin/windows/topologic/TKGeomBase-df87aba5.dll +0 -0
  79. topologicpy/bin/windows/topologic/TKMath-45bd625a.dll +0 -0
  80. topologicpy/bin/windows/topologic/TKMesh-d6e826b1.dll +0 -0
  81. topologicpy/bin/windows/topologic/TKOffset-79b9cc94.dll +0 -0
  82. topologicpy/bin/windows/topologic/TKPrim-aa430a86.dll +0 -0
  83. topologicpy/bin/windows/topologic/TKShHealing-bb48be89.dll +0 -0
  84. topologicpy/bin/windows/topologic/TKTopAlgo-7d0d1e22.dll +0 -0
  85. topologicpy/bin/windows/topologic/TKernel-08c8cfbb.dll +0 -0
  86. topologicpy/bin/windows/topologic/__init__.py +0 -2
  87. topologicpy/bin/windows/topologic/topologic.cp310-win_amd64.pyd +0 -0
  88. topologicpy/bin/windows/topologic/topologic.cp311-win_amd64.pyd +0 -0
  89. topologicpy/bin/windows/topologic/topologic.cp38-win_amd64.pyd +0 -0
  90. topologicpy/bin/windows/topologic/topologic.cp39-win_amd64.pyd +0 -0
  91. topologicpy-0.5.8.dist-info/METADATA +0 -96
  92. topologicpy-0.5.8.dist-info/RECORD +0 -91
  93. {topologicpy-0.5.8.dist-info → topologicpy-6.0.0.dist-info}/WHEEL +0 -0
  94. {topologicpy-0.5.8.dist-info → topologicpy-6.0.0.dist-info}/top_level.txt +0 -0
topologicpy/Helper.py CHANGED
@@ -1,507 +1,507 @@
1
- # Copyright (C) 2024
2
- # Wassim Jabi <wassim.jabi@gmail.com>
3
- #
4
- # This program is free software: you can redistribute it and/or modify it under
5
- # the terms of the GNU Affero General Public License as published by the Free Software
6
- # Foundation, either version 3 of the License, or (at your option) any later
7
- # version.
8
- #
9
- # This program is distributed in the hope that it will be useful, but WITHOUT
10
- # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11
- # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
12
- # details.
13
- #
14
- # You should have received a copy of the GNU Affero General Public License along with
15
- # this program. If not, see <https://www.gnu.org/licenses/>.
16
-
17
- import topologicpy
18
- import os
19
- import warnings
20
-
21
- try:
22
- import numpy as np
23
- import numpy.linalg as la
24
- except:
25
- print("Helper - Installing required numpy library.")
26
- try:
27
- os.system("pip install numpy")
28
- except:
29
- os.system("pip install numpy --user")
30
- try:
31
- import numpy as np
32
- import numpy.linalg as la
33
- print("Helper - numpy library installed correctly.")
34
- except:
35
- warnings.warn("Helper - Error: Could not import numpy.")
36
-
37
- class Helper:
38
- @staticmethod
39
- def ClosestMatch(item, listA):
40
- """
41
- Returns the index of the closest match in the input list to the input item.
42
- This works for lists made out of numeric or string values.
43
-
44
- Parameters
45
- ----------
46
- item : int, float, or str
47
- The input item.
48
- listA : list
49
- The input list.
50
-
51
- Returns
52
- -------
53
- int
54
- The index of the best match in listA for the input item.
55
-
56
- """
57
- import numbers
58
- import random
59
- import string
60
- def levenshtein_distance(s1, s2):
61
- if len(s1) < len(s2):
62
- return levenshtein_distance(s2, s1)
63
-
64
- if len(s2) == 0:
65
- return len(s1)
66
-
67
- previous_row = range(len(s2) + 1)
68
- for i, c1 in enumerate(s1):
69
- current_row = [i + 1]
70
- for j, c2 in enumerate(s2):
71
- insertions = previous_row[j + 1] + 1
72
- deletions = current_row[j] + 1
73
- substitutions = previous_row[j] + (c1 != c2)
74
- current_row.append(min(insertions, deletions, substitutions))
75
- previous_row = current_row
76
-
77
- return previous_row[-1]
78
-
79
- def generate_unlikely_string(length=16):
80
- characters = string.ascii_letters + string.digits + string.punctuation
81
- return ''.join(random.choice(characters) for _ in range(length))
82
-
83
- if not listA:
84
- print("Helper.ClosestMatch - Error: THe input listA parameter is not a valid list. Returning None.")
85
- return None # Handle empty list case
86
-
87
- if isinstance(item, str):
88
- listA = [generate_unlikely_string(length=32) if not isinstance(x, str) else x for x in listA]
89
- # For string inputs, find the closest match using Levenshtein distance
90
- closest_index = min(range(len(listA)), key=lambda i: levenshtein_distance(item, listA[i]))
91
- else:
92
- listA = [float('-inf') if not isinstance(x, numbers.Real) else x for x in listA]
93
- # For numeric or boolean inputs, find the closest match based on absolute difference
94
- closest_index = min(range(len(listA)), key=lambda i: abs(listA[i] - item))
95
-
96
- return closest_index
97
-
98
- @staticmethod
99
- def Flatten(listA):
100
- """
101
- Flattens the input nested list.
102
-
103
- Parameters
104
- ----------
105
- listA : list
106
- The input nested list.
107
-
108
- Returns
109
- -------
110
- list
111
- The flattened list.
112
-
113
- """
114
-
115
- if not isinstance(listA, list):
116
- return [listA]
117
- flat_list = []
118
- for item in listA:
119
- flat_list = flat_list + Helper.Flatten(item)
120
- return flat_list
121
-
122
- @staticmethod
123
- def Iterate(listA):
124
- """
125
- Iterates the input nested list so that each sublist has the same number of members. To fill extra members, the shorter lists are iterated from their first member.
126
- For example Iterate([[1,2,3],['m','n','o','p'],['a','b','c','d','e']]) yields [[1, 2, 3, 1, 2], ['m', 'n', 'o', 'p', 'm'], ['a', 'b', 'c', 'd', 'e']]
127
-
128
- Parameters
129
- ----------
130
- listA : list
131
- The input nested list.
132
-
133
- Returns
134
- -------
135
- list
136
- The iterated list.
137
-
138
- """
139
- # From https://stackoverflow.com/questions/34432056/repeat-elements-of-list-between-each-other-until-we-reach-a-certain-length
140
- def onestep(cur,y,base):
141
- # one step of the iteration
142
- if cur is not None:
143
- y.append(cur)
144
- base.append(cur)
145
- else:
146
- y.append(base[0]) # append is simplest, for now
147
- base = base[1:]+[base[0]] # rotate
148
- return base
149
-
150
- maxLength = len(listA[0])
151
- iterated_list = []
152
- for aSubList in listA:
153
- newLength = len(aSubList)
154
- if newLength > maxLength:
155
- maxLength = newLength
156
- for anItem in listA:
157
- for i in range(len(anItem), maxLength):
158
- anItem.append(None)
159
- y=[]
160
- base=[]
161
- for cur in anItem:
162
- base = onestep(cur,y,base)
163
- iterated_list.append(y)
164
- return iterated_list
165
-
166
- @staticmethod
167
- def K_Means(data, k=4, maxIterations=100):
168
- import random
169
- def euclidean_distance(p, q):
170
- return sum((pi - qi) ** 2 for pi, qi in zip(p, q)) ** 0.5
171
-
172
- # Initialize k centroids randomly
173
- centroids = random.sample(data, k)
174
-
175
- for _ in range(maxIterations):
176
- # Assign each data point to the nearest centroid
177
- clusters = [[] for _ in range(k)]
178
- for point in data:
179
- distances = [euclidean_distance(point, centroid) for centroid in centroids]
180
- nearest_centroid_index = distances.index(min(distances))
181
- clusters[nearest_centroid_index].append(point)
182
-
183
- # Compute the new centroids as the mean of the points in each cluster
184
- new_centroids = []
185
- for cluster in clusters:
186
- if not cluster:
187
- # If a cluster is empty, keep the previous centroid
188
- new_centroids.append(centroids[clusters.index(cluster)])
189
- else:
190
- new_centroids.append([sum(dim) / len(cluster) for dim in zip(*cluster)])
191
-
192
- # Check if the centroids have converged
193
- if new_centroids == centroids:
194
- break
195
-
196
- centroids = new_centroids
197
-
198
- return {'clusters': clusters, 'centroids': centroids}
199
-
200
- @staticmethod
201
- def MergeByThreshold(listA, threshold=0.0001):
202
- """
203
- Merges the numbers in the input list so that numbers within the input threshold are averaged into one number.
204
-
205
- Parameters
206
- ----------
207
- listA : list
208
- The input nested list.
209
- threshold : float , optional
210
- The desired merge threshold value. The default is 0.0001.
211
-
212
- Returns
213
- -------
214
- list
215
- The merged list. The list is sorted in ascending numeric order.
216
-
217
- """
218
- # Sort the list in ascending order
219
- listA.sort()
220
- merged_list = []
221
-
222
- # Initialize the first element in the merged list
223
- merged_list.append(listA[0])
224
-
225
- # Merge numbers within the threshold
226
- for i in range(1, len(listA)):
227
- if listA[i] - merged_list[-1] <= threshold:
228
- # Merge the current number with the last element in the merged list
229
- merged_list[-1] = (merged_list[-1] + listA[i]) / 2
230
- else:
231
- # If the current number is beyond the threshold, add it as a new element
232
- merged_list.append(listA[i])
233
-
234
- return merged_list
235
-
236
- @staticmethod
237
- def MakeUnique(listA):
238
- """
239
- Forces the strings in the input list to be unique if they have duplicates.
240
-
241
- Parameters
242
- ----------
243
- listA : list
244
- The input list of strings.
245
-
246
- Returns
247
- -------
248
- list
249
- The input list, but with each item ensured to be unique if they have duplicates.
250
-
251
- """
252
- # Create a dictionary to store counts of each string
253
- counts = {}
254
- # Create a list to store modified strings
255
- unique_strings = []
256
-
257
- for string in listA:
258
- # If the string already exists in the counts dictionary
259
- if string in counts:
260
- # Increment the count
261
- counts[string] += 1
262
- # Append the modified string with underscore and count
263
- unique_strings.append(f"{string}_{counts[string]}")
264
- else:
265
- # If it's the first occurrence of the string, add it to the counts dictionary
266
- counts[string] = 0
267
- unique_strings.append(string)
268
-
269
- return unique_strings
270
-
271
- @staticmethod
272
- def Normalize(listA, mantissa: int = 6):
273
- """
274
- Normalizes the input list so that it is in the range 0 to 1
275
-
276
- Parameters
277
- ----------
278
- listA : list
279
- The input nested list.
280
- mantissa : int , optional
281
- The desired mantissa value. The default is 4.
282
-
283
- Returns
284
- -------
285
- list
286
- The normalized list.
287
-
288
- """
289
- if not isinstance(listA, list):
290
- print("Helper.Normalize - Error: The input list is not valid. Returning None.")
291
- return None
292
-
293
- # Make sure the list is numeric
294
- l = [x for x in listA if type(x) == int or type(x) == float]
295
- if len(l) < 1:
296
- print("Helper.Normalize - Error: The input list does not contain numeric values. Returning None.")
297
- return None
298
- min_val = min(l)
299
- max_val = max(l)
300
- if min_val == max_val:
301
- normalized_list = [0 for x in l]
302
- else:
303
- normalized_list = [round((x - min_val) / (max_val - min_val), mantissa) for x in l]
304
- return normalized_list
305
-
306
- @staticmethod
307
- def Position(item, listA):
308
- """
309
- Returns the position of the item in the list or the position it would have been inserts.
310
- item is assumed to be numeric. listA is assumed to contain only numeric values and sorted from lowest to highest value.
311
-
312
- Parameters
313
- ----------
314
- item : int or float
315
- The input number to be positioned.
316
- listA : list
317
- The input sorted list.
318
-
319
- Returns
320
- -------
321
- int
322
- The position of the item within the list.
323
-
324
- """
325
- left = 0
326
- right = len(listA) - 1
327
-
328
- while left <= right:
329
- mid = (left + right) // 2
330
- if listA[mid] == item:
331
- return mid
332
- elif listA[mid] < item:
333
- left = mid + 1
334
- else:
335
- right = mid - 1
336
-
337
- # If the target is not found, return the position where it would be inserted
338
- return left
339
-
340
- @staticmethod
341
- def Repeat(listA):
342
- """
343
- Repeats the input nested list so that each sublist has the same number of members. To fill extra members, the last item in the shorter lists are repeated and appended.
344
- For example Iterate([[1,2,3],['m','n','o','p'],['a','b','c','d','e']]) yields [[1, 2, 3, 3, 3], ['m', 'n', 'o', 'p', 'p'], ['a', 'b', 'c', 'd', 'e']]
345
-
346
- Parameters
347
- ----------
348
- listA : list
349
- The input nested list.
350
-
351
- Returns
352
- -------
353
- list
354
- The repeated list.
355
-
356
- """
357
- if not isinstance(listA, list):
358
- return None
359
- repeated_list = [x for x in listA if isinstance(x, list)]
360
- if len(repeated_list) < 1:
361
- return None
362
- maxLength = len(repeated_list[0])
363
- for aSubList in repeated_list:
364
- newLength = len(aSubList)
365
- if newLength > maxLength:
366
- maxLength = newLength
367
- for anItem in repeated_list:
368
- if (len(anItem) > 0):
369
- itemToAppend = anItem[-1]
370
- else:
371
- itemToAppend = None
372
- for i in range(len(anItem), maxLength):
373
- anItem.append(itemToAppend)
374
- return repeated_list
375
-
376
- @staticmethod
377
- def Sort(listA, *otherLists, reverseFlags=None):
378
- """
379
- Sorts the first input list according to the values in the subsequent input lists in order. For example,
380
- your first list can be a list of topologies and the next set of lists can be their volume, surface area, and z level.
381
- The list of topologies will then be sorted first by volume, then by surface, and lastly by z level. You can choose
382
- to reverse the order of sorting by including a list of TRUE/FALSE values in the reverseFlags input parameter.
383
- For example, if you wish to sort the volume in reverse order (from large to small), but sort the other parameters
384
- normally, you would include the following list for reverseFlag: [True, False, False].
385
-
386
- Parameters
387
- ----------
388
- listA : list
389
- The first input list to be sorts
390
- *otherLists : any number of lists to use for sorting listA, optional.
391
- Any number of lists that are used to sort the listA input parameter. The order of these input
392
- parameters determines the order of sorting (from left to right). If no lists are included, the input list will be sorted as is.
393
- reverseFlags : list, optional.
394
- The list of booleans (TRUE/FALSE) to indicated if sorting based on a particular list should be conducted in reverse order.
395
- The length of the reverseFlags list should match the number of the lists in the input otherLists parameter. If set to None,
396
- a default list of FALSE values is created to match the number of the lists in the input otherLists parameter. The default
397
- is None.
398
-
399
- Returns
400
- -------
401
- list
402
- The sorted list.
403
-
404
- """
405
-
406
- # If reverseFlags is not provided, assume all lists should be sorted in ascending order
407
- if reverseFlags is None:
408
- reverseFlags = [False] * len(otherLists)
409
- if not isinstance(otherLists, tuple):
410
- print("Helper.Sort - Error: No other lists to use for sorting have been provided. Returning None.")
411
- return None
412
- if len(otherLists) < 1:
413
- print("Helper.Sort - Error: The otherLists input parameter does not contain any valid lists. Returning None.")
414
- return None
415
- if not len(reverseFlags) == len(otherLists):
416
- print("Helper.Sort - Error: The length of the reverseFlags input parameter is not equal to the number of input lists. Returning None.")
417
- return None
418
- # Convert other_lists to numeric and reverse if needed.
419
- sorting_lists = []
420
- for i, a_list in enumerate(otherLists):
421
- temp_list = []
422
- temp_set = list(set(a_list))
423
- temp_set = sorted(temp_set)
424
- if reverseFlags[i] == True:
425
- temp_set.reverse()
426
- for item in a_list:
427
- temp_list.append(temp_set.index(item))
428
- sorting_lists.append(temp_list)
429
-
430
- combined_lists = list(zip(listA, *sorting_lists))
431
- # Sort the combined list based on all the elements and reverse the lists as needed
432
- combined_lists.sort(key=lambda x: tuple((-val) if reverse else val for val, reverse in zip(x[1:], reverseFlags)))
433
- sorted_listA = [item[0] for item in combined_lists]
434
- return sorted_listA
435
-
436
- @staticmethod
437
- def Transpose(listA):
438
- """
439
- Transposes the input list (swaps rows and columns).
440
-
441
- Parameters
442
- ----------
443
- listA : list
444
- The input list.
445
-
446
- Returns
447
- -------
448
- list
449
- The transposed list.
450
-
451
- """
452
- if not isinstance(listA, list):
453
- return None
454
- length = len(listA[0])
455
- transposed_list = []
456
- for i in range(length):
457
- tempRow = []
458
- for j in range(len(listA)):
459
- tempRow.append(listA[j][i])
460
- transposed_list.append(tempRow)
461
- return transposed_list
462
-
463
- @staticmethod
464
- def Trim(listA):
465
- """
466
- Trims the input nested list so that each sublist has the same number of members. All lists are trimmed to match the length of the shortest list.
467
- For example Trim([[1,2,3],['m','n','o','p'],['a','b','c','d','e']]) yields [[1, 2, 3], ['m', 'n', 'o'], ['a', 'b', 'c']]
468
-
469
- Parameters
470
- ----------
471
- listA : list
472
- The input nested list.
473
-
474
- Returns
475
- -------
476
- list
477
- The repeated list.
478
-
479
- """
480
- minLength = len(listA[0])
481
- returnList = []
482
- for aSubList in listA:
483
- newLength = len(aSubList)
484
- if newLength < minLength:
485
- minLength = newLength
486
- for anItem in listA:
487
- anItem = anItem[:minLength]
488
- returnList.append(anItem)
489
- return returnList
490
-
491
- @staticmethod
492
- def Version():
493
- """
494
- Returns the current version of the software.
495
-
496
- Parameters
497
- ----------
498
- None
499
-
500
-
501
- Returns
502
- -------
503
- str
504
- The current version of the software.
505
-
506
- """
507
- return topologicpy.__version__
1
+ # Copyright (C) 2024
2
+ # Wassim Jabi <wassim.jabi@gmail.com>
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify it under
5
+ # the terms of the GNU Affero General Public License as published by the Free Software
6
+ # Foundation, either version 3 of the License, or (at your option) any later
7
+ # version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful, but WITHOUT
10
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11
+ # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
12
+ # details.
13
+ #
14
+ # You should have received a copy of the GNU Affero General Public License along with
15
+ # this program. If not, see <https://www.gnu.org/licenses/>.
16
+
17
+ import topologicpy
18
+ import os
19
+ import warnings
20
+
21
+ try:
22
+ import numpy as np
23
+ import numpy.linalg as la
24
+ except:
25
+ print("Helper - Installing required numpy library.")
26
+ try:
27
+ os.system("pip install numpy")
28
+ except:
29
+ os.system("pip install numpy --user")
30
+ try:
31
+ import numpy as np
32
+ import numpy.linalg as la
33
+ print("Helper - numpy library installed correctly.")
34
+ except:
35
+ warnings.warn("Helper - Error: Could not import numpy.")
36
+
37
+ class Helper:
38
+ @staticmethod
39
+ def ClosestMatch(item, listA):
40
+ """
41
+ Returns the index of the closest match in the input list to the input item.
42
+ This works for lists made out of numeric or string values.
43
+
44
+ Parameters
45
+ ----------
46
+ item : int, float, or str
47
+ The input item.
48
+ listA : list
49
+ The input list.
50
+
51
+ Returns
52
+ -------
53
+ int
54
+ The index of the best match in listA for the input item.
55
+
56
+ """
57
+ import numbers
58
+ import random
59
+ import string
60
+ def levenshtein_distance(s1, s2):
61
+ if len(s1) < len(s2):
62
+ return levenshtein_distance(s2, s1)
63
+
64
+ if len(s2) == 0:
65
+ return len(s1)
66
+
67
+ previous_row = range(len(s2) + 1)
68
+ for i, c1 in enumerate(s1):
69
+ current_row = [i + 1]
70
+ for j, c2 in enumerate(s2):
71
+ insertions = previous_row[j + 1] + 1
72
+ deletions = current_row[j] + 1
73
+ substitutions = previous_row[j] + (c1 != c2)
74
+ current_row.append(min(insertions, deletions, substitutions))
75
+ previous_row = current_row
76
+
77
+ return previous_row[-1]
78
+
79
+ def generate_unlikely_string(length=16):
80
+ characters = string.ascii_letters + string.digits + string.punctuation
81
+ return ''.join(random.choice(characters) for _ in range(length))
82
+
83
+ if not listA:
84
+ print("Helper.ClosestMatch - Error: THe input listA parameter is not a valid list. Returning None.")
85
+ return None # Handle empty list case
86
+
87
+ if isinstance(item, str):
88
+ listA = [generate_unlikely_string(length=32) if not isinstance(x, str) else x for x in listA]
89
+ # For string inputs, find the closest match using Levenshtein distance
90
+ closest_index = min(range(len(listA)), key=lambda i: levenshtein_distance(item, listA[i]))
91
+ else:
92
+ listA = [float('-inf') if not isinstance(x, numbers.Real) else x for x in listA]
93
+ # For numeric or boolean inputs, find the closest match based on absolute difference
94
+ closest_index = min(range(len(listA)), key=lambda i: abs(listA[i] - item))
95
+
96
+ return closest_index
97
+
98
+ @staticmethod
99
+ def Flatten(listA):
100
+ """
101
+ Flattens the input nested list.
102
+
103
+ Parameters
104
+ ----------
105
+ listA : list
106
+ The input nested list.
107
+
108
+ Returns
109
+ -------
110
+ list
111
+ The flattened list.
112
+
113
+ """
114
+
115
+ if not isinstance(listA, list):
116
+ return [listA]
117
+ flat_list = []
118
+ for item in listA:
119
+ flat_list = flat_list + Helper.Flatten(item)
120
+ return flat_list
121
+
122
+ @staticmethod
123
+ def Iterate(listA):
124
+ """
125
+ Iterates the input nested list so that each sublist has the same number of members. To fill extra members, the shorter lists are iterated from their first member.
126
+ For example Iterate([[1,2,3],['m','n','o','p'],['a','b','c','d','e']]) yields [[1, 2, 3, 1, 2], ['m', 'n', 'o', 'p', 'm'], ['a', 'b', 'c', 'd', 'e']]
127
+
128
+ Parameters
129
+ ----------
130
+ listA : list
131
+ The input nested list.
132
+
133
+ Returns
134
+ -------
135
+ list
136
+ The iterated list.
137
+
138
+ """
139
+ # From https://stackoverflow.com/questions/34432056/repeat-elements-of-list-between-each-other-until-we-reach-a-certain-length
140
+ def onestep(cur,y,base):
141
+ # one step of the iteration
142
+ if cur is not None:
143
+ y.append(cur)
144
+ base.append(cur)
145
+ else:
146
+ y.append(base[0]) # append is simplest, for now
147
+ base = base[1:]+[base[0]] # rotate
148
+ return base
149
+
150
+ maxLength = len(listA[0])
151
+ iterated_list = []
152
+ for aSubList in listA:
153
+ newLength = len(aSubList)
154
+ if newLength > maxLength:
155
+ maxLength = newLength
156
+ for anItem in listA:
157
+ for i in range(len(anItem), maxLength):
158
+ anItem.append(None)
159
+ y=[]
160
+ base=[]
161
+ for cur in anItem:
162
+ base = onestep(cur,y,base)
163
+ iterated_list.append(y)
164
+ return iterated_list
165
+
166
+ @staticmethod
167
+ def K_Means(data, k=4, maxIterations=100):
168
+ import random
169
+ def euclidean_distance(p, q):
170
+ return sum((pi - qi) ** 2 for pi, qi in zip(p, q)) ** 0.5
171
+
172
+ # Initialize k centroids randomly
173
+ centroids = random.sample(data, k)
174
+
175
+ for _ in range(maxIterations):
176
+ # Assign each data point to the nearest centroid
177
+ clusters = [[] for _ in range(k)]
178
+ for point in data:
179
+ distances = [euclidean_distance(point, centroid) for centroid in centroids]
180
+ nearest_centroid_index = distances.index(min(distances))
181
+ clusters[nearest_centroid_index].append(point)
182
+
183
+ # Compute the new centroids as the mean of the points in each cluster
184
+ new_centroids = []
185
+ for cluster in clusters:
186
+ if not cluster:
187
+ # If a cluster is empty, keep the previous centroid
188
+ new_centroids.append(centroids[clusters.index(cluster)])
189
+ else:
190
+ new_centroids.append([sum(dim) / len(cluster) for dim in zip(*cluster)])
191
+
192
+ # Check if the centroids have converged
193
+ if new_centroids == centroids:
194
+ break
195
+
196
+ centroids = new_centroids
197
+
198
+ return {'clusters': clusters, 'centroids': centroids}
199
+
200
+ @staticmethod
201
+ def MergeByThreshold(listA, threshold=0.0001):
202
+ """
203
+ Merges the numbers in the input list so that numbers within the input threshold are averaged into one number.
204
+
205
+ Parameters
206
+ ----------
207
+ listA : list
208
+ The input nested list.
209
+ threshold : float , optional
210
+ The desired merge threshold value. The default is 0.0001.
211
+
212
+ Returns
213
+ -------
214
+ list
215
+ The merged list. The list is sorted in ascending numeric order.
216
+
217
+ """
218
+ # Sort the list in ascending order
219
+ listA.sort()
220
+ merged_list = []
221
+
222
+ # Initialize the first element in the merged list
223
+ merged_list.append(listA[0])
224
+
225
+ # Merge numbers within the threshold
226
+ for i in range(1, len(listA)):
227
+ if listA[i] - merged_list[-1] <= threshold:
228
+ # Merge the current number with the last element in the merged list
229
+ merged_list[-1] = (merged_list[-1] + listA[i]) / 2
230
+ else:
231
+ # If the current number is beyond the threshold, add it as a new element
232
+ merged_list.append(listA[i])
233
+
234
+ return merged_list
235
+
236
+ @staticmethod
237
+ def MakeUnique(listA):
238
+ """
239
+ Forces the strings in the input list to be unique if they have duplicates.
240
+
241
+ Parameters
242
+ ----------
243
+ listA : list
244
+ The input list of strings.
245
+
246
+ Returns
247
+ -------
248
+ list
249
+ The input list, but with each item ensured to be unique if they have duplicates.
250
+
251
+ """
252
+ # Create a dictionary to store counts of each string
253
+ counts = {}
254
+ # Create a list to store modified strings
255
+ unique_strings = []
256
+
257
+ for string in listA:
258
+ # If the string already exists in the counts dictionary
259
+ if string in counts:
260
+ # Increment the count
261
+ counts[string] += 1
262
+ # Append the modified string with underscore and count
263
+ unique_strings.append(f"{string}_{counts[string]}")
264
+ else:
265
+ # If it's the first occurrence of the string, add it to the counts dictionary
266
+ counts[string] = 0
267
+ unique_strings.append(string)
268
+
269
+ return unique_strings
270
+
271
+ @staticmethod
272
+ def Normalize(listA, mantissa: int = 6):
273
+ """
274
+ Normalizes the input list so that it is in the range 0 to 1
275
+
276
+ Parameters
277
+ ----------
278
+ listA : list
279
+ The input nested list.
280
+ mantissa : int , optional
281
+ The desired mantissa value. The default is 6.
282
+
283
+ Returns
284
+ -------
285
+ list
286
+ The normalized list.
287
+
288
+ """
289
+ if not isinstance(listA, list):
290
+ print("Helper.Normalize - Error: The input list is not valid. Returning None.")
291
+ return None
292
+
293
+ # Make sure the list is numeric
294
+ l = [x for x in listA if type(x) == int or type(x) == float]
295
+ if len(l) < 1:
296
+ print("Helper.Normalize - Error: The input list does not contain numeric values. Returning None.")
297
+ return None
298
+ min_val = min(l)
299
+ max_val = max(l)
300
+ if min_val == max_val:
301
+ normalized_list = [0 for x in l]
302
+ else:
303
+ normalized_list = [round((x - min_val) / (max_val - min_val), mantissa) for x in l]
304
+ return normalized_list
305
+
306
+ @staticmethod
307
+ def Position(item, listA):
308
+ """
309
+ Returns the position of the item in the list or the position it would have been inserts.
310
+ item is assumed to be numeric. listA is assumed to contain only numeric values and sorted from lowest to highest value.
311
+
312
+ Parameters
313
+ ----------
314
+ item : int or float
315
+ The input number to be positioned.
316
+ listA : list
317
+ The input sorted list.
318
+
319
+ Returns
320
+ -------
321
+ int
322
+ The position of the item within the list.
323
+
324
+ """
325
+ left = 0
326
+ right = len(listA) - 1
327
+
328
+ while left <= right:
329
+ mid = (left + right) // 2
330
+ if listA[mid] == item:
331
+ return mid
332
+ elif listA[mid] < item:
333
+ left = mid + 1
334
+ else:
335
+ right = mid - 1
336
+
337
+ # If the target is not found, return the position where it would be inserted
338
+ return left
339
+
340
+ @staticmethod
341
+ def Repeat(listA):
342
+ """
343
+ Repeats the input nested list so that each sublist has the same number of members. To fill extra members, the last item in the shorter lists are repeated and appended.
344
+ For example Iterate([[1,2,3],['m','n','o','p'],['a','b','c','d','e']]) yields [[1, 2, 3, 3, 3], ['m', 'n', 'o', 'p', 'p'], ['a', 'b', 'c', 'd', 'e']]
345
+
346
+ Parameters
347
+ ----------
348
+ listA : list
349
+ The input nested list.
350
+
351
+ Returns
352
+ -------
353
+ list
354
+ The repeated list.
355
+
356
+ """
357
+ if not isinstance(listA, list):
358
+ return None
359
+ repeated_list = [x for x in listA if isinstance(x, list)]
360
+ if len(repeated_list) < 1:
361
+ return None
362
+ maxLength = len(repeated_list[0])
363
+ for aSubList in repeated_list:
364
+ newLength = len(aSubList)
365
+ if newLength > maxLength:
366
+ maxLength = newLength
367
+ for anItem in repeated_list:
368
+ if (len(anItem) > 0):
369
+ itemToAppend = anItem[-1]
370
+ else:
371
+ itemToAppend = None
372
+ for i in range(len(anItem), maxLength):
373
+ anItem.append(itemToAppend)
374
+ return repeated_list
375
+
376
+ @staticmethod
377
+ def Sort(listA, *otherLists, reverseFlags=None):
378
+ """
379
+ Sorts the first input list according to the values in the subsequent input lists in order. For example,
380
+ your first list can be a list of topologies and the next set of lists can be their volume, surface area, and z level.
381
+ The list of topologies will then be sorted first by volume, then by surface, and lastly by z level. You can choose
382
+ to reverse the order of sorting by including a list of TRUE/FALSE values in the reverseFlags input parameter.
383
+ For example, if you wish to sort the volume in reverse order (from large to small), but sort the other parameters
384
+ normally, you would include the following list for reverseFlag: [True, False, False].
385
+
386
+ Parameters
387
+ ----------
388
+ listA : list
389
+ The first input list to be sorts
390
+ *otherLists : any number of lists to use for sorting listA, optional.
391
+ Any number of lists that are used to sort the listA input parameter. The order of these input
392
+ parameters determines the order of sorting (from left to right). If no lists are included, the input list will be sorted as is.
393
+ reverseFlags : list, optional.
394
+ The list of booleans (TRUE/FALSE) to indicated if sorting based on a particular list should be conducted in reverse order.
395
+ The length of the reverseFlags list should match the number of the lists in the input otherLists parameter. If set to None,
396
+ a default list of FALSE values is created to match the number of the lists in the input otherLists parameter. The default
397
+ is None.
398
+
399
+ Returns
400
+ -------
401
+ list
402
+ The sorted list.
403
+
404
+ """
405
+
406
+ # If reverseFlags is not provided, assume all lists should be sorted in ascending order
407
+ if reverseFlags is None:
408
+ reverseFlags = [False] * len(otherLists)
409
+ if not isinstance(otherLists, tuple):
410
+ print("Helper.Sort - Error: No other lists to use for sorting have been provided. Returning None.")
411
+ return None
412
+ if len(otherLists) < 1:
413
+ print("Helper.Sort - Error: The otherLists input parameter does not contain any valid lists. Returning None.")
414
+ return None
415
+ if not len(reverseFlags) == len(otherLists):
416
+ print("Helper.Sort - Error: The length of the reverseFlags input parameter is not equal to the number of input lists. Returning None.")
417
+ return None
418
+ # Convert other_lists to numeric and reverse if needed.
419
+ sorting_lists = []
420
+ for i, a_list in enumerate(otherLists):
421
+ temp_list = []
422
+ temp_set = list(set(a_list))
423
+ temp_set = sorted(temp_set)
424
+ if reverseFlags[i] == True:
425
+ temp_set.reverse()
426
+ for item in a_list:
427
+ temp_list.append(temp_set.index(item))
428
+ sorting_lists.append(temp_list)
429
+
430
+ combined_lists = list(zip(listA, *sorting_lists))
431
+ # Sort the combined list based on all the elements and reverse the lists as needed
432
+ combined_lists.sort(key=lambda x: tuple((-val) if reverse else val for val, reverse in zip(x[1:], reverseFlags)))
433
+ sorted_listA = [item[0] for item in combined_lists]
434
+ return sorted_listA
435
+
436
+ @staticmethod
437
+ def Transpose(listA):
438
+ """
439
+ Transposes the input list (swaps rows and columns).
440
+
441
+ Parameters
442
+ ----------
443
+ listA : list
444
+ The input list.
445
+
446
+ Returns
447
+ -------
448
+ list
449
+ The transposed list.
450
+
451
+ """
452
+ if not isinstance(listA, list):
453
+ return None
454
+ length = len(listA[0])
455
+ transposed_list = []
456
+ for i in range(length):
457
+ tempRow = []
458
+ for j in range(len(listA)):
459
+ tempRow.append(listA[j][i])
460
+ transposed_list.append(tempRow)
461
+ return transposed_list
462
+
463
+ @staticmethod
464
+ def Trim(listA):
465
+ """
466
+ Trims the input nested list so that each sublist has the same number of members. All lists are trimmed to match the length of the shortest list.
467
+ For example Trim([[1,2,3],['m','n','o','p'],['a','b','c','d','e']]) yields [[1, 2, 3], ['m', 'n', 'o'], ['a', 'b', 'c']]
468
+
469
+ Parameters
470
+ ----------
471
+ listA : list
472
+ The input nested list.
473
+
474
+ Returns
475
+ -------
476
+ list
477
+ The repeated list.
478
+
479
+ """
480
+ minLength = len(listA[0])
481
+ returnList = []
482
+ for aSubList in listA:
483
+ newLength = len(aSubList)
484
+ if newLength < minLength:
485
+ minLength = newLength
486
+ for anItem in listA:
487
+ anItem = anItem[:minLength]
488
+ returnList.append(anItem)
489
+ return returnList
490
+
491
+ @staticmethod
492
+ def Version():
493
+ """
494
+ Returns the current version of the software.
495
+
496
+ Parameters
497
+ ----------
498
+ None
499
+
500
+
501
+ Returns
502
+ -------
503
+ str
504
+ The current version of the software.
505
+
506
+ """
507
+ return topologicpy.__version__