cytriangle 1.0.0__cp312-cp312-macosx_13_0_x86_64.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.

Potentially problematic release.


This version of cytriangle might be problematic. Click here for more details.

@@ -0,0 +1,5 @@
1
+ from cytriangle.ctriangle cimport triangulateio
2
+
3
+ cdef class TriangleIO:
4
+ cdef int out_flag
5
+ cdef triangulateio* _io
@@ -0,0 +1,547 @@
1
+ from libc.stdlib cimport free, malloc
2
+ import numpy as np
3
+ from cytriangle.ctriangle cimport triangulateio
4
+
5
+ def validate_input_attributes(attributes):
6
+ num_attr = list(set([len(sublist) for sublist in attributes]))
7
+ if len(num_attr) > 1:
8
+ raise ValueError(
9
+ "Attribute lists must have the same number of attributes for each element"
10
+ )
11
+ return num_attr[0]
12
+
13
+
14
+ def validate_attribute_number(attributes, base_quantity):
15
+ if len(attributes) != base_quantity:
16
+ raise ValueError(
17
+ "Attribute list must have the same number of elements as the input it decorates"
18
+ )
19
+
20
+ cdef class TriangleIO:
21
+
22
+ def __cinit__(self):
23
+ # Initialize the triangulateio struct with NULL pointers
24
+ self._io = <triangulateio*> NULL
25
+ # out flag prevents freeing shared in / out struct data twice
26
+ self.out_flag = 0
27
+
28
+ def __dealloc__(self):
29
+ # Free allocated memory when the instance is deallocated
30
+ if self._io is not NULL:
31
+ # add all the allocation releases
32
+ if self._io.pointlist is not NULL:
33
+ free(self._io.pointlist)
34
+ if self._io.pointattributelist is not NULL:
35
+ free(self._io.pointattributelist)
36
+ if self._io.pointmarkerlist is not NULL:
37
+ free(self._io.pointmarkerlist)
38
+ if self._io.trianglelist is not NULL:
39
+ free(self._io.trianglelist)
40
+ if self._io.triangleattributelist is not NULL:
41
+ free(self._io.triangleattributelist)
42
+ if self._io.trianglearealist is not NULL:
43
+ free(self._io.trianglearealist)
44
+ if self._io.neighborlist is not NULL:
45
+ free(self._io.neighborlist)
46
+ if self._io.segmentlist is not NULL:
47
+ free(self._io.segmentlist)
48
+ if self._io.segmentmarkerlist is not NULL:
49
+ free(self._io.segmentmarkerlist)
50
+ if self._io.holelist is not NULL and self.out_flag != 1:
51
+ free(self._io.holelist)
52
+ if self._io.regionlist is not NULL and self.out_flag != 1:
53
+ free(self._io.regionlist)
54
+ if self._io.edgelist is not NULL:
55
+ free(self._io.edgelist)
56
+ if self._io.edgemarkerlist is not NULL:
57
+ free(self._io.edgemarkerlist)
58
+ if self._io.normlist is not NULL:
59
+ free(self._io.normlist)
60
+ free(self._io)
61
+
62
+ def __init__(self, input_dict=None,kind=''):
63
+ # Prevent double deallocation on 'out' triangleio structs
64
+ if kind == 'out':
65
+ self.out_flag = 1
66
+ # Assemble the triangulateio struct from a Python dictionary (default)
67
+ self._io = <triangulateio*> malloc(sizeof(triangulateio))
68
+
69
+ # Allocate null fields
70
+ self._io.pointlist = <double*> NULL
71
+ self._io.numberofpoints = 0
72
+
73
+ self._io.pointattributelist = <double*> NULL
74
+ self._io.numberofpointattributes = 0
75
+ self._io.pointmarkerlist = <int*> NULL
76
+
77
+ self._io.trianglelist = <int*> NULL
78
+ self._io.numberoftriangles = 0
79
+ self._io.numberofcorners = 0
80
+ self._io.numberoftriangleattributes = 0
81
+ self._io.triangleattributelist = <double*> NULL
82
+ self._io.trianglearealist = <double*> NULL
83
+ self._io.neighborlist = <int*> NULL
84
+
85
+ # input - p switch
86
+ self._io.segmentlist = <int*> NULL
87
+ self._io.segmentmarkerlist = <int*> NULL
88
+ self._io.numberofsegments = 0
89
+
90
+ # input - p switch without r
91
+ self._io.holelist = <double*> NULL
92
+ self._io.numberofholes = 0
93
+ self._io.regionlist = <double*> NULL
94
+ self._io.numberofregions = 0
95
+
96
+ # input - always ignored
97
+ self._io.edgelist = <int*> NULL
98
+ self._io.edgemarkerlist = <int*> NULL
99
+ self._io.normlist = <double*> NULL
100
+ self._io.numberofedges = 0
101
+
102
+ # Populate based on input_dict
103
+ if input_dict is not None:
104
+ if 'vertices' in input_dict:
105
+ self.set_vertices(input_dict['vertices'])
106
+ # set other vertex related optional fields
107
+ if 'vertex_attributes' in input_dict:
108
+ self.set_vertex_attributes(input_dict['vertex_attributes'])
109
+ if 'vertex_markers' in input_dict:
110
+ self.set_vertex_markers(input_dict['vertex_markers'])
111
+ if 'triangles' in input_dict:
112
+ # fetch number of corners from triangle input
113
+ self._io.numberofcorners = len(input_dict['triangles'][0])
114
+ self.set_triangles(input_dict['triangles'])
115
+ if 'triangle_attributes' in input_dict:
116
+ self.set_triangle_attributes(input_dict['triangle_attributes'])
117
+ if 'triangle_max_area' in input_dict:
118
+ self.set_triangle_areas(input_dict['triangle_max_area'])
119
+ if 'segments' in input_dict:
120
+ self.set_segments(input_dict['segments'])
121
+ if 'segment_markers' in input_dict:
122
+ self.set_segment_markers(input_dict['segment_markers'])
123
+ if 'holes' in input_dict:
124
+ self.set_holes(input_dict['holes'])
125
+ if 'regions' in input_dict:
126
+ self.set_regions(input_dict['regions'])
127
+
128
+ def to_dict(self,opt=''):
129
+ """
130
+ Converts the internal C TriangleIO data structure into a dictionary format.
131
+
132
+ Parameters:
133
+ - opt: A string that indicates the format of the output. If 'np', numpy arrays are used.
134
+
135
+ Returns:
136
+ - A dictionary containing the triangulation data.
137
+ """
138
+ output_dict = {}
139
+
140
+ if opt == 'np':
141
+ if self.vertices:
142
+ output_dict['vertices'] = np.asarray(self.vertices)
143
+ if self.vertex_attributes:
144
+ output_dict['vertex_attributes'] = np.asarray(self.vertex_attributes)
145
+ if self.vertex_markers:
146
+ output_dict['vertex_markers'] = np.asarray(self.vertex_markers)
147
+ if self.triangles:
148
+ output_dict['triangles'] = np.asarray(self.triangles)
149
+ if self.triangle_attributes:
150
+ output_dict['triangle_attributes'] = np.asarray(self.triangle_attributes)
151
+ else:
152
+ if self.vertices:
153
+ output_dict['vertices'] = self.vertices
154
+ if self.vertex_attributes:
155
+ output_dict['vertex_attributes'] = self.vertex_attributes
156
+ if self.vertex_markers:
157
+ output_dict['vertex_markers'] = self.vertex_markers
158
+ if self.triangles:
159
+ output_dict['triangles'] = self.triangles
160
+ if self.triangle_attributes:
161
+ output_dict['triangle_attributes'] = self.triangle_attributes
162
+ if self.triangle_max_area:
163
+ output_dict['triangle_max_area'] = self.triangle_max_area
164
+ if self.neighbors:
165
+ output_dict['neighbors'] = self.neighbors
166
+ if self.segments:
167
+ output_dict['segments'] = self.segments
168
+ if self.segment_markers:
169
+ output_dict['segment_markers'] = self.segment_markers
170
+ if self.holes:
171
+ output_dict['holes'] = self.holes
172
+ if self.regions:
173
+ output_dict['regions'] = self.regions
174
+ if self.edges:
175
+ output_dict['edges'] = self.edges
176
+ if self.edge_markers:
177
+ output_dict['edge_markers'] = self.edge_markers
178
+ if self.norms:
179
+ output_dict['norms'] = self.norms
180
+
181
+ return output_dict
182
+
183
+ @property
184
+ def vertices(self):
185
+ """
186
+ `vertices`: A list of pairs [x, y] that are vertex coordinates.
187
+
188
+ Returns:
189
+ - A list of pairs [x, y] that are vertex coordinates.
190
+ """
191
+ if self._io.pointlist is not NULL:
192
+ return [[self._io.pointlist[2*i], self._io.pointlist[2*i + 1]]
193
+ for i in range(self._io.numberofpoints)]
194
+
195
+ @vertices.setter
196
+ def vertices(self, vertices):
197
+ self.set_vertices(vertices)
198
+
199
+ @property
200
+ def vertex_attributes(self):
201
+ """
202
+ `vertex_attributes`: An list of lists of vertex attributes (floats).
203
+ Each vertex must have the same number of attributes, and
204
+ len(vertex_attributes) must match the number of points.
205
+
206
+ Returns:
207
+ - A list of lists, where each inner list contains attributes for a vertex.
208
+ """
209
+ if self._io.pointattributelist is not NULL:
210
+ vertex_attributes = []
211
+ for i in range(self._io.numberofpoints):
212
+ vertex_attr = []
213
+ for j in range(self._io.numberofpointattributes):
214
+ vertex_attr.append(
215
+ self._io.pointattributelist[i*self._io.numberofpointattributes + j ]
216
+ )
217
+ vertex_attributes.append(vertex_attr)
218
+ return vertex_attributes
219
+
220
+ @vertex_attributes.setter
221
+ def vertex_attributes(self, vertex_attributes):
222
+ self.set_vertex_attributes(vertex_attributes)
223
+
224
+ @property
225
+ def vertex_markers(self):
226
+ """
227
+ `vertex_markers`: A list of vertex markers; one int per point.
228
+
229
+ Returns:
230
+ - A list of integers representing markers for each vertex.
231
+ """
232
+ if self._io.pointmarkerlist is not NULL:
233
+ return [self._io.pointmarkerlist[i] for i in range(self._io.numberofpoints)]
234
+
235
+ @vertex_markers.setter
236
+ def vertex_markers(self, vertex_markers):
237
+ self.set_vertex_markers(vertex_markers)
238
+
239
+ @property
240
+ def triangles(self):
241
+ """
242
+ `triangles`: A list of triangle corners (not necessarily 3). Corners are designated
243
+ in a counterclockwise order, followed by any other nodes if the triangle represents a
244
+ nonlinear element (e.g. num_corners > 3).
245
+
246
+ Returns:
247
+ - A list of lists, where each inner list contains vertex indices for a triangle.
248
+ """
249
+ if self._io.trianglelist is not NULL:
250
+ triangles = []
251
+ for i in range(self._io.numberoftriangles):
252
+ tri_order = []
253
+ for j in range(self._io.numberofcorners):
254
+ tri_order.append(self._io.trianglelist[i * self._io.numberofcorners + j])
255
+ triangles.append(tri_order)
256
+ return triangles
257
+
258
+ @triangles.setter
259
+ def triangles(self, triangles):
260
+ self.set_triangles(triangles)
261
+
262
+ @property
263
+ def triangle_attributes(self):
264
+ """
265
+ `triangle_attributes`: A list of triangle attributes. Each triangle must have
266
+ the same number of attributes.
267
+
268
+ Returns:
269
+ - A list of lists, where each inner list contains attributes for a triangle.
270
+ """
271
+ if self._io.triangleattributelist is not NULL:
272
+ triangle_attributes = []
273
+ for i in range(self._io.numberoftriangles):
274
+ triangle_attr = []
275
+ for j in range(self._io.numberoftriangleattributes):
276
+ triangle_attr.append(
277
+ self._io.triangleattributelist[i*self._io.numberoftriangleattributes + j]
278
+ )
279
+ triangle_attributes.append(triangle_attr)
280
+ return triangle_attributes
281
+
282
+ @triangle_attributes.setter
283
+ def triangle_attributes(self, triangle_attributes):
284
+ self.set_triangle_attributes(triangle_attributes)
285
+
286
+ @property
287
+ def triangle_max_area(self):
288
+ """
289
+ `triangle_max_area`: A list of triangle area constraints; one per triangle, 0 if not set.
290
+ Input only.
291
+
292
+ Returns:
293
+ - A list of floats representing the maximum area for each triangle.
294
+ """
295
+ if self._io.trianglearealist is not NULL:
296
+ return [self._io.trianglearealist[i]
297
+ for i in range(self._io.numberoftriangles)]
298
+
299
+ @triangle_max_area.setter
300
+ def triangle_max_area(self, triangle_areas):
301
+ self.set_triangle_areas(triangle_areas)
302
+
303
+ @property
304
+ def neighbors(self):
305
+ """
306
+ `neighbors`: A list of triangle neighbors; three ints per triangle. Output only.
307
+
308
+ Returns:
309
+ - A list of lists, where each inner list contains indices of neighboring triangles.
310
+ """
311
+ max_neighbors = 3
312
+ if self._io.neighborlist is not NULL:
313
+ neighbor_list = []
314
+ for i in range(self._io.numberoftriangles):
315
+ neighbors = [self._io.neighborlist[i*max_neighbors + j]
316
+ for j in range(max_neighbors)]
317
+ # remove sentinel values (-1)
318
+ neighbors = [neighbor for neighbor in neighbors if neighbor != -1]
319
+ neighbor_list.append(neighbors)
320
+ return neighbor_list
321
+
322
+ @property
323
+ def segments(self):
324
+ """
325
+ `segments`: A list of segment endpoints.
326
+
327
+ Returns:
328
+ - A list of lists, where each inner list contains vertex indices for
329
+ a segment.
330
+ """
331
+ if self._io.segmentlist is not NULL:
332
+ segments = []
333
+ for i in range(self._io.numberofsegments):
334
+ start_pt_index = self._io.segmentlist[2 * i]
335
+ end_pt_index = self._io.segmentlist[2 * i + 1]
336
+ segments.append([start_pt_index, end_pt_index])
337
+ return segments
338
+
339
+ @segments.setter
340
+ def segments(self, segments):
341
+ self.set_segments(segments)
342
+
343
+ @property
344
+ def holes(self):
345
+ """
346
+ `holes`: A list of hole coordinates.
347
+
348
+ Returns:
349
+ - A list of pairs [x, y] representing coordinates of holes.
350
+ """
351
+ if self._io.holelist is not NULL:
352
+ return [[self._io.holelist[2*i], self._io.holelist[2*i + 1]]
353
+ for i in range(self._io.numberofholes)]
354
+
355
+ @holes.setter
356
+ def holes(self, holes):
357
+ self.set_holes(holes)
358
+
359
+ # unmarked segments have a value of 0
360
+ @property
361
+ def segment_markers(self):
362
+ """
363
+ `segment_markers`: An array of segment markers; one int per segment.
364
+
365
+ Returns:
366
+ - A list of integers representing markers for each segment.
367
+ """
368
+ if self._io.segmentmarkerlist is not NULL:
369
+ segment_markers = []
370
+ for i in range(self._io.numberofsegments):
371
+ segment_markers.append(self._io.segmentmarkerlist[i])
372
+ return segment_markers
373
+
374
+ @segment_markers.setter
375
+ def segment_markers(self, segment_markers):
376
+ self.set_segment_markers(segment_markers)
377
+
378
+ @property
379
+ def regions(self):
380
+ """
381
+ `regions`: An array of regional attributes and area constraints. Note that
382
+ each regional attribute is used only if you select the `A` switch, and each area
383
+ constraint is used only if you select the `a` switch (with no number following).
384
+
385
+ Returns:
386
+ - A list of dictionaries, each containing 'vertex' (coordinates of the region's
387
+ vertex), 'marker' (integer marker for the region), and 'max_area' (maximum area
388
+ constraint for the region).
389
+ """
390
+ if self._io.regionlist is not NULL:
391
+ regions = []
392
+ for i in range(self._io.numberofregions):
393
+ region = {}
394
+ region['vertex'] = [self._io.regionlist[4*i], self._io.regionlist[4*i + 1]]
395
+ region['marker'] = int(self._io.regionlist[4*i + 2])
396
+ region['max_area'] = self._io.regionlist[4*i + 3]
397
+ regions.append(region)
398
+ return regions
399
+
400
+ @regions.setter
401
+ def regions(self, regions):
402
+ self.set_regions(regions)
403
+
404
+ @property
405
+ def edges(self):
406
+ """
407
+ `edges`: An array of edge endpoints. The first edge's endpoints are at
408
+ indices [0] and [1], followed by the remaining edges. Two ints per
409
+ edge. Output only.
410
+
411
+ Returns:
412
+ - A list of lists, where each inner list contains vertex indices for an edge.
413
+ """
414
+ if self._io.edgelist is not NULL:
415
+ edges = []
416
+ for i in range(self._io.numberofedges):
417
+ edges.append([self._io.edgelist[i * 2], self._io.edgelist[i * 2 + 1]])
418
+ return edges
419
+
420
+ @property
421
+ def edge_markers(self):
422
+ """
423
+ `edge_markers`: An array of edge markers; one int per edge. Output only.
424
+
425
+ Returns:
426
+ - A list of integers representing markers for each edge.
427
+ """
428
+ if self._io.edgemarkerlist is not NULL:
429
+ return [self._io.edgemarkerlist[i] for i in range(self._io.numberofedges)]
430
+
431
+ @property
432
+ def norms(self):
433
+ """
434
+ `norms`: An array of normal vectors, used for infinite rays in Voronoi
435
+ diagrams. For each finite edge in a Voronoi diagram, the normal vector written
436
+ is the zero vector. Output only.
437
+
438
+ Returns:
439
+ - A list of dictionaries, each containing 'ray_origin' (start
440
+ point of the ray) and 'ray_direction' (direction of the ray),
441
+ represented as [x, y] pairs.
442
+ """
443
+ if self._io.normlist is not NULL:
444
+ norm_list = []
445
+ for i in range(self._io.numberofedges):
446
+ norm_list.append({'ray_origin': [self._io.normlist[i * 4],
447
+ self._io.normlist[i * 4 + 1]],
448
+ 'ray_direction': [self._io.normlist[i * 4 + 2],
449
+ self._io.normlist[i * 4 + 3]]})
450
+ return norm_list
451
+
452
+ def set_vertices(self, vertices):
453
+ num_vertices = len(vertices)
454
+ self._io.numberofpoints = num_vertices
455
+ if num_vertices < 3:
456
+ raise ValueError('Valid input requires three or more vertices')
457
+ vertices = np.ascontiguousarray(vertices)
458
+ self._io.pointlist = <double*>malloc(2 * num_vertices * sizeof(double))
459
+ for i in range(num_vertices):
460
+ self._io.pointlist[2 * i] = vertices[i, 0]
461
+ self._io.pointlist[2 * i + 1] = vertices[i, 1]
462
+
463
+ def set_vertex_attributes(self, vertex_attributes):
464
+ num_attr = validate_input_attributes(vertex_attributes)
465
+ num_vertices = self._io.numberofpoints
466
+ validate_attribute_number(vertex_attributes, num_vertices)
467
+ vertex_attributes = np.ascontiguousarray(vertex_attributes)
468
+ self._io.pointattributelist = <double*>malloc(num_attr * num_vertices * sizeof(double))
469
+ self._io.numberofpointattributes = num_attr
470
+ for i in range(num_vertices):
471
+ for j in range(num_attr):
472
+ self._io.pointattributelist[i * num_attr + j] = vertex_attributes[i, j]
473
+
474
+ def set_vertex_markers(self, vertex_markers):
475
+ vertex_markers = np.ascontiguousarray(vertex_markers, dtype=int)
476
+ self._io.pointmarkerlist = <int*>malloc(len(vertex_markers) * sizeof(int))
477
+ for i in range(len(vertex_markers)):
478
+ self._io.pointmarkerlist[i] = vertex_markers[i]
479
+
480
+ def set_triangles(self, triangles):
481
+ num_triangles = len(triangles)
482
+ num_corners = self._io.numberofcorners
483
+ triangles = np.ascontiguousarray(triangles, dtype=int)
484
+ self._io.trianglelist = <int*>malloc(num_triangles * num_corners * sizeof(int))
485
+ self._io.numberoftriangles = num_triangles
486
+ for i in range(num_triangles):
487
+ for j in range(num_corners):
488
+ self._io.trianglelist[i*num_corners + j] = triangles[i, j]
489
+
490
+ def set_triangle_attributes(self, triangle_attributes):
491
+ num_attr = validate_input_attributes(triangle_attributes)
492
+ num_triangles = self._io.numberoftriangles
493
+ validate_attribute_number(triangle_attributes, num_triangles)
494
+ triangle_attributes = np.ascontiguousarray(triangle_attributes)
495
+ self._io.triangleattributelist = <double*>malloc(num_attr * num_triangles * sizeof(double))
496
+ self._io.numberoftriangleattributes = num_attr
497
+ for i in range(num_triangles):
498
+ for j in range(num_attr):
499
+ self._io.triangleattributelist[i * num_attr + j] = triangle_attributes[i, j]
500
+
501
+ def set_triangle_areas(self, triangle_areas):
502
+ num_triangles = self._io.numberoftriangles
503
+ validate_attribute_number(triangle_areas, num_triangles)
504
+ triangle_max_area = np.ascontiguousarray(triangle_areas)
505
+ self._io.trianglearealist = <double*>malloc(num_triangles * sizeof(double))
506
+ for i in range(num_triangles):
507
+ self._io.trianglearealist[i] = triangle_max_area[i]
508
+
509
+ def set_segments(self, segments):
510
+ num_segments = len(segments)
511
+ self._io.numberofsegments = num_segments
512
+ segments = np.ascontiguousarray(segments, dtype=int)
513
+ self._io.segmentlist = <int*>malloc(num_segments * 2 * sizeof(int))
514
+ for i in range(num_segments):
515
+ self._io.segmentlist[i * 2] = segments[i, 0]
516
+ self._io.segmentlist[i * 2 + 1] = segments[i, 1]
517
+
518
+ def set_segment_markers(self, segment_markers):
519
+ segment_markers = np.ascontiguousarray(segment_markers, dtype=int)
520
+ validate_attribute_number(segment_markers, self._io.numberofsegments)
521
+ self._io.segmentmarkerlist = <int*>malloc(self._io.numberofsegments * sizeof(int))
522
+ for i in range(self._io.numberofsegments):
523
+ self._io.segmentmarkerlist[i] = segment_markers[i]
524
+
525
+ def set_holes(self, holes):
526
+ num_holes = len(holes)
527
+ self._io.numberofholes = num_holes
528
+ holes = np.ascontiguousarray(holes)
529
+ self._io.holelist = <double*>malloc(num_holes * 2 * sizeof(double))
530
+ for i in range(num_holes):
531
+ self._io.holelist[2 * i] = holes[i, 0]
532
+ self._io.holelist[2 * i + 1] = holes[i, 1]
533
+
534
+ def set_regions(self, regions):
535
+ num_regions = len(regions)
536
+ self._io.numberofregions = num_regions
537
+ # unpack region dict
538
+ region_array = [[region['vertex'][0],
539
+ region['vertex'][1],
540
+ region['marker'],
541
+ region['max_area']]
542
+ for region in regions]
543
+ regions = np.ascontiguousarray(region_array)
544
+ self._io.regionlist = <double*>malloc(num_regions * 4 * sizeof(double))
545
+ for i in range(num_regions):
546
+ for j in range(4):
547
+ self._io.regionlist[i * 4 + j] = regions[i, j]