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