Skip to main content

stripack_sys/
ffi.rs

1use std::ffi::{c_double, c_int};
2
3unsafe extern "C" {
4    /**
5    Adds a node to a triangulation of the convex hull of nodes `1, ..., k-1`, producing a
6    triangulation of the convex hull of nodes `1, ..., k`.
7
8    The algorithm consists of the following steps: node `k` is located relative to the
9    triangulation ([trfind]), its index is added to the data structure ([intadd] or [bdyadd]), and a sequence of swaps ([swptst] and [swap]) are applied to the arcs opposite `k` so that all arcs incident on node `k` and opposite node `k` are locally optimal (statisfy the circumcircle test).
10
11    Thus, if a Delaunay triangulation of nodes `1` through `k-1` is input, a Delaunay
12    triangulation of nodes `1` through `k` will be output.
13
14    # Arguments
15
16    * `nst` - Input. The index of a node at which [trfind] begins its search. Search time depends on
17      the proximity of this node to `k`. If `nst < 1`, the search is begun at node `k-1`.
18
19    * `k` - Input. The nodal index (index for `x`, `y`, and `z`, and `lend`) of the new node
20      to be added. `4 <= k`.
21
22    * `x[k]`, `y[k]`, `z[k]` - Input. The coordinates of the nodes.
23    * `list[6 * (n - 2)]`, `lptr[6 * (n - 2)]`, `lend[k]`, `lnew` - Input/Output. On input, the data
24      structure associated with the triangulation of nodes `1` to `k-1`. On output, the data has been
25      updated to include node `k`. The array lengths are assumed to be large enough to add node `k`.
26      Refer to [trmesh].
27    * `ier` - Output. Error indicator:
28      * `0` if no errors were encountered.
29      * `-1` if `k` is outside its valid range on input.
30      * `-2` if all nodes (including `k`) are collinear (lie on a common geodesic).
31      * `l` if nodes `l` and `k` coincide for some `l < k`
32    */
33    #[link_name = "addnod_"]
34    pub fn addnod(
35        nst: *const c_int,
36        k: *const c_int,
37        x: *const c_double,
38        y: *const c_double,
39        z: *const c_double,
40        list: *mut c_int,
41        lptr: *mut c_int,
42        lend: *mut c_int,
43        lnew: *mut c_int,
44        ier: *mut c_int,
45    );
46
47    /**
48    Computes the arc cosine function, with argument truncation.
49
50    If you call your system `acos` routine with an input argument that is outside the range `[-1.0,
51    1.0]` you may get an unpleasant surprise. This routine truncates arguments outside the range.
52    # Arguments
53
54    * `c` - Input. The argument
55
56    # Returns
57    * An angle whose cosine is `c`
58    */
59    #[link_name = "arc_cosine_"]
60    pub fn arc_cosine(c: *const c_double) -> c_double;
61
62    /**
63    Computes the area of a spherical triangle on the unit sphere.
64
65    # Arguments
66    * `v1[3]`, `v2[3]`, `v3[3]` - Input. The Cartesian coordinates of unit vectors (the three triangle
67      vertices in any order). These vectors, if nonzero, are implicitly scaled to have length `1`.
68
69    # Returns
70    The area of the spherical triangle defined by `v1`, `v2`, and `v3`, in the range `0` to
71    `2*PI` (the area of a hemisphere). `0` if and only if `v1`, `v2`, and `v3` lie in (or close to)
72    a plane containing the origin.
73
74    # Safety
75    - All pointers must be valid and properly aligned
76    - Arrays `v1`, `v2`, `v3` must have length == `3`
77    */
78    #[link_name = "areas_"]
79    pub fn areas(v1: *const c_double, v2: *const c_double, v3: *const c_double) -> c_double;
80
81    /**
82    Adds a boundary node to a triangulation.
83
84    This subroutine adds a boundary node to a triangulation of a set
85    of `kk - 1` points on the unit sphere. The data structure is
86    updated with the insertion of node `kk`, but no optimizaiton is
87    performed.
88
89    This routine is identical to the similarly named routine in
90    TRIPACK.
91
92    # Arguments
93
94    * `kk` - Input. The index of a node to be connected to the
95      sequence of all visible boundary nodes. 1 <= `kk` and
96      `kk` must not be equal to `i1` or `i2`.
97    * `i1` - Input. The first (rightmost as viewed from `kk`)
98      boundary node in the triangulation that is visible from
99      node `kk` (the line segment `kk-i1` intersects no arcs).
100    * `i2` - Input. The last (leftmost) boundary node that is visible
101      from node `kk`. `i1` and `i2` may be determined by [trfind].
102    * `list[6 * (n - 2)]`, `lptr[6 * (n - 2)]`, `lend[n]`, `lnew` - Input/output. The
103      triangulation data structure created by [trmesh]. Nodes `i1` and `i2` must be included in the triangulation. On output, the data structure is updated with the addition of node `kk`. Node `kk` is connected to `i1`, `i2`, and all boundary nodes in between.
104     */
105    #[link_name = "bdyadd_"]
106    pub fn bdyadd(
107        kk: *const c_int,
108        i1: *const c_int,
109        i2: *const c_int,
110        list: *mut c_int,
111        lptr: *mut c_int,
112        lend: *mut c_int,
113        lnew: *mut c_int,
114    );
115
116    /**
117    Returns the boundary nodes of a triangulation.
118
119    Given a triangulation of `n` nodes on the
120    unit sphere created by [trmesh], this subroutine returns an array containing the indexes (if any) of the counterclockwise sequence of boundary nodes, that is, the nodes on the boundary of the convex hull of the set of nodes. The boundary is empty if the nodes do not lie in a single hemisphere. The numbers of boundary nodes, arcs, and triangles are also returned.
121
122    # Arguments
123
124    * `n` - Input. The number of nodes in the triangulation. `3 <= n`.
125    * `list[6 * (n - 2)]`, `lptr[6 * (n - 2)]`, `lend[n]` - Input. The data structure
126      defining the triangulation, created by [trmesh].
127    * `nodes` - Output. The ordered sequence of `nb` boundary node indexes in the range `1` to `n`.
128      For safety, the dimension of `nodes` should be `n`.
129    * `nb` - Output. The number of boundary nodes.
130      `na`, `nt` - Output. The number of arcs and triangles, repectively, in the triangulation.
131    */
132    #[link_name = "bnodes_"]
133    pub fn bnodes(
134        n: *const c_int,
135        list: *const c_int,
136        lptr: *const c_int,
137        lend: *const c_int,
138        nodes: *mut c_int,
139        nb: *mut c_int,
140        na: *mut c_int,
141        nt: *mut c_int,
142    );
143
144    /**
145    Returns the circumcenter of a spherical triangle.
146
147    Returns the circumcenter of a spherical triangle on the unit sphere: the point on the
148    sphere surface that is equally distant from the three triangle vertices and lies in the same
149    hemisphere, where distance is taken to be arc-length on the sphere surface.
150
151    # Arguments
152    * `v1[3]`, `v2[3]`, `v3[3]` - Input. The coordinates of the three triangle vertices (unit
153      vectors) in counterclockwise order.
154    * `c[3]` - Output. The coordinates of the circumcenter unless `0 < ier`, which in case `c` is
155      not defined. `c = (v2 - v1) X (v3 - v1)` normalized to a unit vector.
156    * `ier` - Output. Error indicator:
157      * `0`, if no errors were encountered.
158      * `1`, if `v1`, `v2`, and `v3` lie on a common line: `(v2 - v1) X (v3 - v1) = 0`.
159    */
160    #[link_name = "circum_"]
161    pub fn circum(
162        v1: *const c_double,
163        v2: *const c_double,
164        v3: *const c_double,
165        c: *mut c_double,
166        ier: *mut c_int,
167    );
168
169    /**
170    Connects an exterior node to boundary nodes, covering the sphere.
171
172    This subroutine connects an exterior node `kk` to all boundary nodes of a triangulation of `kk - 1` points on the unit sphere, producing a triangulation that covers the sphere. The data structure is updated with the addition of node `kk`, but no optimization is performed. All boundary nodes must be visible from node `kk`.
173
174    # Arguments
175
176    * `kk` - Input. Index of the node to be connected to the set of all boundary nodes. `4 <= kk`.
177    * `n0` - Input. Index of a boundary node (in the range `1` to `kk - 1`). `n0` may be
178      determined by [trfind].
179    * `list[6 * (n - 2)]`, `lptr[6 * (n - 2)]`, `lend[n]`, `lnew` - Input/output. The
180      triangulation data structure created by [trmesh]. Node `n0` must be included in the triangulation. On output, updated with the addition of node `kk` as the last entry. The updated triangulation contains no boundary nodes.
181    */
182    #[link_name = "covsph_"]
183    pub fn covsph(
184        kk: *const c_int,
185        n0: *const c_int,
186        list: *mut c_int,
187        lptr: *mut c_int,
188        lend: *mut c_int,
189        lnew: *mut c_int,
190    );
191
192    /**
193    Returns triangle circumcenters and other information.
194
195    Given a Delaunay triangulation of nodes on the surface of the unit sphere, this subroutine returns the set of triangle circumcenters corresponding to Voronoi vertices, along with the circumradii and a list of triangle indexes `listc` stored in one-to-one correspondence with `list/lptr` entries.
196
197    A triangle circumcenter is the point (unit vector) lying at the same angular distance from the three vertices and contained in the same hemisphere as the vertices. (Note that the negative of a circumcenter is also equidistant from the vertices.) If the triangulation covers the surface, the Voronoi vertices are the circumenters of the triangles in the Delaunay triangulation. `lptr`, `lend`, and `lnew` are not altered in this case.
198
199    On the other hand, if the nodes are contained in a single hemisphere, the triangulation is implicitly extended to the entire surface by adding pseudo-arcs (of length greater than 180 degrees) between boundary nodes forming pseudo-triangles whose 'circumcenters' are included in the list. This extension to the triangulation actually consists of a triangulation of the set of boundary nodes in which the swap test is reversed (a non-empty circumcircle test). The negative circumcenters are stored as the pseudo-triangle 'circumcenters'. `listc`, `lptr`, `lend`, and `lnew` contain a data structure corresponding to the extended triangulation (Voronoi diagram), but `list` is not altered in this case. Thus, if it is necessary to retain the original (unextended) triangulation data structure, copies of `lptr` and `lnew` must be saved before calling this routine.
200
201    # Arguments
202
203    * `n` - Input. The number of nodes in the triangulation `3 <= n`. Note that, if `n = 3`,
204      there are only two Voronoi vertices separated by 180 degrees, and the Voronoi regions are not well defined.
205    * `ncol` - Input. The number of columns reserved for `ltri`. This must be at least `nb - 2`,
206      where `nb` is the number of boundary nodes.
207    * `x[n]`, `y[n]`, `z[n]` - Input. The coordinates of the nodes (unit vectors).
208    * `list[6 * (n - 2)]` - Input. The set of adjacency lists. Refer to [trmesh].
209    * `lend[n]` - Input. The set of pointers to ends of adjacency lists. Refer to [trmesh].
210    * `lptr[6 * (n - 2)]` - Input/output. Pointers associated with `list`. Refer to [trmesh]. On
211      output, pointers associated with `listc`. Updated for the addition of pseudo-triangles if the original triangulation contains boundary nodes (`0 < nb`).
212    * `lnew` - Input/output. On input, a pointer to the first empty location in `list` and `lptr` (list length plus one). On output, pointer to the first empty location in `listc` and `lptr` (list length plus one). `lnew` is not altered if `nb = 0`.
213    * `ltri[6][ncol]` - Output. Triangle list whose first `nb - 2` columns contain the indexes
214      of a clockwise-ordered sequence of vertices (first three rows) followed by the `ltri` column indexes of the triangles opposite the vertices (or 0 denoting the exterior region) in the last three rows. This array is not generally of any further use outside this routine.
215    * `listc[3 * nt]` - Output. Where `nt = 2 * n - 4` is the number of triangles in the
216      triangulation (after extending it to cover the entire surface if necessary). Contains the triangle indexes (indexes to `xc`, `yc`, `zc`, and `rc`) stored in 1-1 correspondence with `list`/`lptr` entries (or entries that would be stored in `list` for the extended triangulation): the index of triangle (`n1`, `n2`, `n3`) is stored in `listc[k]`, `listc[l]`, and `listc[m]`, where `list[k]`, `list[l]`, and `list[m]` are the indexes of `n2` as a neighbor of `n1`, `n3` as a neighbor of `n2`, and `n1` as a neighbor of `n3`. The Voronoi region associated with a node is defined by the CCW-ordered sequence of circumcenters in one-to-one correspondence with its adjacency list (in the extended triangulation).
217    * `nb` - Output. The number of boundary nodes unless `ier = 1`.
218    * `xc[2 * n - 4]`, `yc[2 * n - 4]`, `zc[2 * n - 4]`, the coordinates of the triangle
219      circumcenters (Voronoi vertices). `xc[i]**2 + yc[i]**2 + zc[i]**2 = 1`. The first `nb - 2`
220      entries correspond to pseudo-triangles if `0 < nb`.
221    * `rc[2 * n - 4]` - Output. The circumradii (the arc lengths or angles between the
222      circumcenters and associated triangle vertices) in 1-1 correspondence with circumcenters.
223    * `ier` - Output. Error indicator:
224      * `0`, if no errors were encountered.
225      * `1`, if `n < 3`.
226      * `2`, if `ncol < nb - 2`.
227      * `3`, if a triangle is degenerate (has vertices lying on a common geodesic).
228    */
229    #[link_name = "crlist_"]
230    pub fn crlist(
231        n: *const c_int,
232        ncol: *const c_int,
233        x: *const c_double,
234        y: *const c_double,
235        z: *const c_double,
236        list: *const c_int,
237        lend: *const c_int,
238        lptr: *mut c_int,
239        lnew: *mut c_int,
240        ltri: *mut c_int,
241        listc: *mut c_int,
242        nb: *mut c_int,
243        xc: *mut c_double,
244        yc: *mut c_double,
245        zc: *mut c_double,
246        rc: *mut c_double,
247        ier: *mut c_int,
248    );
249
250    /**
251    Deletes a boundary arc from a triangulation.
252
253    This subroutine deletes a boundary arc from a triangulation. It may be used to remove a null triangle from the convex hull boundary. Note, however, that if the union of triangles is rendered nonconvex, subroutines [delnod], [edge], and [trfind] (and hence [addnod]) may fail. Also, function [nearnd] should not be called following an arc deletion.
254
255    This routine is identical to the similarly named routine in TRIPACK.
256
257    # Arguments
258
259    * `n` - Input. The number of nodes in the triangulation. `4 <= n`.
260    * `io1`, `io2` - Input. Indexes (in the range of `1` to `n`) of a pair of adjacent boundary
261      nodes defining the arc to be removed.
262    * `list[6 * (n - 2)]`, `lptr[6 * (n - 2)]`, `lend[n]`, `lnew` - Input/output. The triangulation
263      data structure created by [trmesh]. On output, updated with the removal of arc `io1-io2` unless `0 < ier`.
264    * `ier` - Output. Error indicator:
265      * `0`, if no errors were encountered.
266      * `1`, if `n`, `io1`, or `io2` is outside its valid range, or `io1 = io2`.
267      * `2`, if `io1-io2` is not a boundary arc.
268      * `3`, if the node opposite `io1-io2` is already a boundary node, and thus `io1` or `io2` has only two neighbors or a deletion would result in two triangulations sharing a single node.
269      * `4`, if one of the nodes is a neighbor of the other, but not vice versa, implying an invalid triangulation data structure.
270     */
271    #[link_name = "delarc_"]
272    pub fn delarc(
273        n: *const c_int,
274        io1: *const c_int,
275        io2: *const c_int,
276        list: *mut c_int,
277        lptr: *mut c_int,
278        lend: *mut c_int,
279        lnew: *mut c_int,
280        ier: *mut c_int,
281    );
282
283    /**
284    Deletes a neighbor from the adjacency list.
285
286    This subroutine deletes a neighbor `nb` from the adjacency list of node `n0` (but `n0` is not deleted from the adjacency list of `nb`) and, if `nb` is a boundary node, makes `n0` a boundary node.
287
288    For pointer (`list` index) `lph` to `nb` as a neighbor of `n0`, the empty `list`, `lptr` location `lph` is filled in with the values at `lnew - 1`, pointer `lnew - 1` (in `lptr` and possibly in `lend`) is changed to `lph`, and `lnew` is decremented.
289
290    This requires a search of `lend` and `lptr` entailing an expected operation count of O(n).
291
292    This routine is identical to the similarly named routine in TRIPACK.
293
294    # Arguments
295
296    * `n0`, `nb` - Input. Indexes, in the range `1` to `n`, of a pair of nodes such that `nb` is
297      a neighbor of `n0`. (`n0` need not be a neighbor of `nb`.)
298    * `n` - Input. The number of nodes in the triangulation. `3 <= n`.
299    * `list[6 * (n - 2)]`, `lptr[6 * (n - 2)]`, `lend[n]`, `lnew` - Input/Output. The data structure defining
300      the triangulation. On output, updated with the removal of `nb` from the adjacency list of
301      `n0` unless `lph < 0`.
302    * `lph` - Input/output. List pointer to the hole (`nb` as a neighbor of `n0`) filled in by
303      the values at `lnew - 1` or error indicator:
304      `> 0`, if no errors were encountered.
305      `-1`, if `n0`, `nb`, or `n` is outside its valid range.
306      `-2`, if `nb` is not a neighbor of `n0`.
307    */
308    #[link_name = "delnb_"]
309    pub fn delnb(
310        n0: *const c_int,
311        nb: *const c_int,
312        n: *const c_int,
313        list: *mut c_int,
314        lptr: *mut c_int,
315        lend: *mut c_int,
316        lnew: *mut c_int,
317        lph: *mut c_int,
318    );
319
320    /**
321    Deletes a node from a triangulation.
322
323    This subroutine deletes node `k` (along with all arcs incident on node `k`) from a triangulation of `n` nodes on the unit sphere, and inserts arcs as necessary to produce a triangulation of the remaining `n - 1` nodes. If a Delaunay triangulation is input, a Delaunay triangulation will result, and thus, [delnod] reverses the effect of a call to [addnod].
324
325    Note that the deletion may result in all remaining nodes being collinear. This situation is not flagged.
326
327    # Arguments
328
329    * `k` - Input. The index (for `x`, `y`, and `z`) of the node to be deleted. `1 <= k <= n`.
330    * `n` - Input/output. The number of nodes in the triangulation. `4 <= n`. Note that `n` will
331      be decremented following the deletion.
332    * `x[n]`, `y[n]`, `z[n]` - Input/output. The coordinates of the nodes in the triangulation.
333      On output, updated with elements `k + 1, ..., n + 1` shifted up one position, thus overwriting element `k`, unless `1 <= ier <= 4`.
334    * `list[6 * (n - 2)]`, `lptr[6 * (n - 2)]`, `lend[n]`, `lnew` - Input/output. The data
335      structure defining the triangulation, created by [trmesh]. On output, updated to reflect the deletion unless `1 <= ier <= 4`. Note that the data structure may have been altered if `3 < ier`.
336    * `lwk` - Input/output. The number of columns reserved for `iwk`. `lkw` must be at least
337      `nnb-3`, where `nnb` is the number of neighbors of node `k`, including an extra pseudo-node if `k` is a boundary node. On output, the number of `iwk` columns required unless `ier = 1` or `ier = 3`.
338    * `iwk[2][lwk]` - Output. The indexes of the endpoints of the new arcs added unless `lwk = 0` or `1 <= ier <= 4`. (Arcs are associated with columns.)
339    * `ier` - Output. Error indicator:
340      * `0`, if no errors were encountered
341      * `1`, if `k` or `n` is outside its valid range or `lwk < 0` on input.
342      * `2`, if more space is required in `iwk`. Refer to `lwk`.
343      * `3`, if the triangulation data structure is invalid on input.
344      * `4`, if `k` indexes an interior node with four or more neighbors, none of which can be swapped out due to collinearity, and `k` cannot therefore be deleted.
345      * `5`, if an error flag (other than `ier = 1`) was returned by [optim]. An error message is written to the standard output unit in this case.
346      * `6`, if error flag `1` was returned by [optim]. This is not necessarily an error, but the arcs may not be optimal.
347    */
348    #[link_name = "delnod_"]
349    pub fn delnod(
350        k: *const c_int,
351        n: *const c_int,
352        x: *const c_double,
353        y: *const c_double,
354        z: *const c_double,
355        list: *mut c_int,
356        lptr: *mut c_int,
357        lend: *mut c_int,
358        lnew: *mut c_int,
359        lwk: *mut c_int,
360        iwk: *mut c_int,
361        ier: *mut c_int,
362    );
363
364    /**
365    Swaps arcs to force two nodes to be adjacent.
366
367    Given a triangulation of `n` nodes and a pair of nodal indexes `in1` and `in2`, this routine
368    swaps arcs as necessary to force `in1` and `in2` to be adjacent. Only arcs which intersect `in1
369    -in2` are swapped out. If a Delaunay triangulation is input, the resulting triangulation is as
370    close as possible to a Delaunay triangulation in the sense that all arcs other than `in1-in2`
371    are locally optimal.
372
373    A sequence of calls to [edge] may be used to force the presence of a set of edges defining the
374    boundary of a non-convex and/or multiply connected region, or to introduce barriers into the
375    triangulation. Note that [getnp] will not necessarily return closest nodes if the triangulation
376    has been constrained by a call to [edge]. However, this is appropriate in some applications,
377    such as triangle-based interpolation on a nonconvex domain.
378
379    # Arguments
380
381    * `in1`, `in2` - Input. The indexes (of `x`, `y`, and `z`) in the range `1` to `n` defining a pair of
382      nodes to be connected by an arc.
383    * `x[n]`, `y[n]`, `z[n]` - Input. The coordinates of the nodes.
384    * `lwk` - Input/output. On input, the number of columns reserved for `iwk`. This must be at
385      least `ni`, the number of arcs that intersect `in1-in2`. (`ni` is bounded by `n - 3`.) On
386      output, the number of arcs which intersect `in1-in2` (but not more than the input value of `lwk`) unless `ier = 1` or `ier = 3`. `lwk = 0` if and only if `in1` and `in2` were adjacent (or `lwk = 0`) on input.
387    * `iwk[2 * lwk]` - Output. The indexes of the endpoints of the new arcs other than `in1-in2`
388      unless `0 < ier` or `lwk = 0`. New arcs to the left of `in1->in2` are stored in the first `k-1`
389      columns (left portion of `iwk`), column `k` contains zeros, and new arcs to the right of
390      `in1->in2` occupy columns `k + 1, ..., lwk`. (`k` can be determined by searching `iwk` for the
391      zeros.)
392    * `list[6 * (n - 2)]`, `lptr[6 * (n - 2)]`, `lend[n]` - Input/output. The data structure
393      defining the triangulation, created by [trmesh]. On output, updated if necessary to refelct the
394      presence of an arc connecting `in1` and `in2` unless `0 < ier`. The data structure has been
395      altered if `4 <= ier`.
396    * `ier` - Output. Error indicator:
397      * `0`, if no errors were encountered
398      * `1`, if `in1 < 1`, `in2 < 1`, `in1 = in2`, or `lwk < 0` on input.
399      * `2`, if more space is required in `iwk`. Refer to `lwk`.
400      * `3`, if `in1` and `in2` could not be connected due to either an invalid data structure or
401        collinear nodes (and floating point error).
402      * `4`, if an error flag other than `ier = 1` was returned by [optim]
403      * `5`, if error flag `1` was returned by [optim]. This is not necessarily an error, but the arcs
404        other than `in1-in2` may not be optimal
405    */
406    #[link_name = "edge_"]
407    pub fn edge(
408        in1: *const c_int,
409        in2: *const c_int,
410        x: *const c_double,
411        y: *const c_double,
412        z: *const c_double,
413        lwk: *mut c_int,
414        iwk: *mut c_int,
415        list: *mut c_int,
416        lptr: *mut c_int,
417        lend: *mut c_int,
418        ier: *mut c_int,
419    );
420
421    /**
422    Gets the next nearest node to a given node.
423
424    Given a Delaunay triangulation of `n` nodes on the unit sphere and an array `npts` containing the indexes of `l-1` nodes ordered by angular distance from `npts[1]`, this routine sets `npts[l]` to the index of the next node in the sequence -- the node, other than `npts[1], ..., npts[l - 1]`, that is closest to `npts[1]`. Thus, the ordered sequence of `k` closest nodes to `n1` (including `n1`) may be determined by `k - 1` calls to `getnp` with `npts[1] = n1` and `l = 2,3,..., k` for `k >= 2`.
425
426    The algorithm uses the property of a Delaunay triangulation that the `k`-th closest node to `n1` is a neighbor of one of the `k-1` closest nodes to `n1`.
427
428    # Arguments
429
430    * `x[n]`, `y[n]`, `z[n]` - Input. The coordinates of the nodes.
431    * `list[6 * (n - 2)]`, `lptr[6 * (n - 2)]`, `lend[n]` - Input. The triangulation data
432      structure, created by [trmesh].
433    * `l` - Input. The number of nodes in the sequence on output. `2 <= l <= n`.
434    * `npts[l]` - Input/output. On input, the indexes of the `l - 1` closest nodes to `npts[1]`
435      in the first `l - 1` locations. On output, updated with the index of the `l`-th closest
436      node to `npts[1]` in position `l` unless `ier = 1`.
437    * `df` - Output. The value of an increasing function (negative cosine) of the angular
438      distance between `npts[1]` and `npts[l]` unless `ier = 1`.
439    * `ier` - Output. Error indicator:
440      `0`, if no errors were encountered.
441      `1`, if `l < 2`.
442    */
443    #[link_name = "getnp_"]
444    pub fn getnp(
445        x: *const c_double,
446        y: *const c_double,
447        z: *const c_double,
448        list: *const c_int,
449        lptr: *const c_int,
450        lend: *const c_int,
451        l: *const c_int,
452        npts: *mut c_int,
453        df: *mut c_double,
454        ier: *mut c_int,
455    );
456
457    /**
458    Inserts `k` as a neighbor of `n1`.
459
460    This subroutine inserts `k` as a neighbor of `n1` following `n2`,
461    where `lp` is the `list` pointer of `n2` as a neighbor of `n1`.
462    Note that, if `n2` is the last neighbor of `n1`, K will become
463    the first neighbor (even if `n1` is a boundary node).
464
465    This routine is identical to the similarly named routine in
466    TRIPACK.
467
468    # Arguments
469
470    * `k` - Input. The index of the node to be inserted.
471    * `lp` - Input. The `list` pointer of `n2` as a neighbor of `n1`.
472    * `list[6 * (n - 2)]`, `lptr[6 * (n - 2)]`, `lnew` - Input/Output.
473      The data structure defining the triangulation, created by [trmesh]. On output, updated with the addition of node `k`
474    */
475    #[link_name = "insert_"]
476    pub fn insert(
477        k: *const c_int,
478        lp: *const c_int,
479        list: *mut c_int,
480        lptr: *mut c_int,
481        lnew: *mut c_int,
482    );
483
484    /**
485    Determines if a point is inside a polygonal region.
486
487    This function locates a point `p` relative to a polygonal region `r` on the surface of the unit sphere, returning `inside = true` if and only if `p` is contained in `r`. `r` is defined by a cyclically ordered sequence of vertices which form a positively-oriented simple closed curve. Adjacent vertices need not be distinct but the curve must not be self-intersecting. Also, while polygon edges are by definition restricted to a single hemisphere, `r` is not so restricted. Its interior is the region to the left as the vertices are traversed in order.
488
489    The algorithm consists of selecting a point `q` in `r` and then finding all points at which the great circle defined by `p` and `q` intersects the boundary of `r`. `p` lies inside `r` if and only if there is an even number of intersection points between `q` and `p`. `q` is taken to be a point immediately to the left of a directed boundary edge -- the first one that results in no consistency-check failures.
490
491    If `p` is close to the polygon boundary, the problem is ill-conditioned and the decision may be incorrect. Also, an incorrect decision may result from a poor choice of `q` (if, for example, a boundary edge lies on the great circle defined by `p` and `q`). A more reliable result could be obtained by a sequence of calls to [inside] with the vertices cyclically permuted before each call (to alter the choice of `q`).
492
493    # Arguments
494
495    * `p[3]` - Input. The coordinates of the point (unit vector) to be located.
496    * `lv` - Input. The length of the arrays `xv`, `yv`, and `zv`.
497    * `xv[lv]`, `yv[lv]`, `zv[lv]` - Input. The coordinates of unit vectors (points on the unit sphere).
498    * `nv` - Input. The number of vertices in the polygon. `3 <= nv <= lv`.
499    * `listv[nv]` - Input. The indexes (for `xv`, `yv`, and `zv`) of a cyclically-ordered (and
500      CCW-ordered) sequence of vertices that define `r`. The last vertex (indexed by `listv[nv]`) is followed by the first (indexed by `listv[1]`). `listv` entries must be in the range `1` to `lv`.
501    * `ier` - Output. Error indicator:
502      * `0`, if no errors were encountered.
503      * `1`, if `lv` or `nv` is outside its valid range.
504      * `2`, if a `listv` entry is outside its valid range.
505      * `3`, if the polygon boundary was found to be self-intersecting. This error will not necessarily be detected.
506      * `4`, if every choice of `q` (one for each boundary edge) led to failure of some internal consistency check. The most likely cause of this error is invalid input: `p = (0, 0, 0)`, a null or self-intersecting polygon, etc.
507
508    # Returns
509
510    True if and only if `p` lies inside `r` unless `ier != 0`, in which case the value is not altered.
511
512    */
513    #[link_name = "inside_"]
514    pub fn inside(
515        p: *const c_double,
516        lv: *const c_int,
517        xv: *const c_double,
518        yv: *const c_double,
519        zv: *const c_double,
520        nv: *const c_int,
521        listv: *const c_int,
522        ier: *mut c_int,
523    ) -> bool;
524
525    /**
526    Adds an interior node to a triangulation.
527
528    This subroutine adds an interior node to a triangulation of a set of points on the unit sphere. The data structure is updated with the insertion of node `kk` into the triangle whose vertices are `i1`, `i2`, and `i3`. No optimization of the triangulation is performed.
529
530    This routine is identical to the similarly named routine in TRIPACK.
531
532    # Arguments
533
534    * `kk` - Input. The index of the node to be inserted. `1 <= kk` and `kk` must not be equal
535      to `i1`, `i2`, or `i3`.
536    * `i1`, `i2`, `i3` - Input. Indexes of the counterclockwise-orderd sequence of vertices of a
537      triangle which contains node `kk`.
538    * `list[6 * (n - 2)]`, `lptr[6 * (n - 2)]`, `lend[n]`, `lnew` - Input/output. The data structure
539      defining the triangulation, created by [trmesh]. Triangle (`i1`, `i2`, `i3`) must be included in the triangulation. On output, updated with the addition of node `kk`. `kk` will be connected to nodes `i1`, `i2`, and `i3`.
540    */
541    #[link_name = "intadd_"]
542    pub fn intadd(
543        kk: *const c_int,
544        i1: *const c_int,
545        i2: *const c_int,
546        i3: *const c_int,
547        list: *mut c_int,
548        lptr: *mut c_int,
549        lend: *mut c_int,
550        lnew: *mut c_int,
551    );
552
553    /**
554    Finds the intersection of two great circles.
555
556    Given a great circle `c` and points `p1` and `p2` defining an arc `a` on the surface of the unit sphere, where `a` is the shorter of the two portions of the great circle `c12` associated with `p1` and `p2`, this subroutine returns the point of intersection `p` between `c` and `c12` that is closer to `a`. Thus, if `p1` and `p2` lie in opposite hemispheres defined by `c`, `p` is the point of intersection of `c` with `a`.
557
558    # Arguments
559
560    * `p1[3]`, `p2[3]` - Input. The coordinates of unit vectors.
561    * `cn[3]` - Input. The coordinates of a nonzero vector which defines `c` as the intersection
562      of the plane whose normal is `cn` with the unit sphere. Thus, if `c` is to be the great
563      circle defined by `p` and `q`, `cn` should be `p X q`.
564    * `p` - Output. Point of intersection defined above unless `ier` is not 0, in which case `p`
565      is not altered.
566    * `ier` - Output. Error indicator:
567      * `0`, if no errors were encountered.
568      * `1`, if `<cn, p1> = <cn, p2>`. This occurs if and only if `p1 = p2` or `cn = 0` or there are two intersection points at the same distance from `a`.
569      * `2` if `p2 = -p1` and the definition of `a` is therefore ambiguous.
570    */
571    #[link_name = "intrsc_"]
572    pub fn intrsc(
573        p1: *const c_double,
574        p2: *const c_double,
575        cn: *const c_double,
576        p: *mut c_double,
577        ier: *mut c_int,
578    );
579
580    /**
581    Returns a random integer between `1` and `n`.
582
583    This function returns a uniformly distributed pseudorandom integer in the range `1` to `n`.
584
585    # Arguments
586
587    * `n` - Input. The maximum value to be returned.
588    * `ix`, `iy`, `iz` - Input/output. The Seeds initialized to values in the range `1` to
589      `30,000` before the first call to `jrand`, and not altered between subsequent calls (unless a sequence of random numbers is to be repeated by reinitializing the seeds).
590
591    # Returns
592    A random integer in the range `1` to `n`.
593    */
594    #[link_name = "jrand_"]
595    pub fn jrand(n: *const c_int, ix: *mut c_int, iy: *mut c_int, iz: *mut c_int) -> c_int;
596
597    /**
598    Determines whether a node is left of a plane through the origin.
599
600    This function determines whether node `n0` is in the (closed) left hemisphere defined by the
601    plane containing `n1`, `n2`, and the origin, where left is defined relative to an observer at
602    `n1` facing `n2`.
603
604    # Arguments
605
606    * `x1`, `y1`, `z1` - Input. The coordinates of `n1`.
607    * `x2`, `y2`, `z2` - Input. The coordinates of `n2`.
608    * `x0`, `y0`, `z0` - Input. The coordinates of `n0`.
609
610    # Returns
611    True if and only if `n0` is in the closed left hemisphere.
612    */
613    #[link_name = "left_"]
614    pub fn left(
615        x1: *const c_double,
616        y1: *const c_double,
617        z1: *const c_double,
618        x2: *const c_double,
619        y2: *const c_double,
620        z2: *const c_double,
621        x0: *const c_double,
622        y0: *const c_double,
623        z0: *const c_double,
624    ) -> bool;
625
626    /**
627    Returns the index of `nb` in the adjacency list.
628
629    This function returns the index (`list` pointer) of `nb` in the adjacency list for `n0`, where `lpl = lend[n0]`. This function is identical to the similarly named function in TRIPACK.
630
631    # Arguments
632    * `lpl` - Input. Is `lend[n0]`
633    * `nb` - Input. The index of the node whose pointer is to be returned. `nb` must be connected to
634      `n0`.
635    * `list[6 * (n - 2)]`, `lptr[6 * (n - 2)]` - Input. The data structure defining the
636      triangulation, created by [trmesh].
637
638    # Returns
639    Pointer `lstptr` such that `list[lstptr] = nb` or `list[lstptr] = -nb`, unless `nb`
640      is not a neighbor of `n0`, in which case `lstptr = lpl`.
641    */
642    #[link_name = "lstptr_"]
643    pub fn lstptr(
644        lpl: *const c_int,
645        nb: *const c_int,
646        list: *const c_int,
647        lptr: *const c_int,
648    ) -> c_int;
649
650    /**
651    Returns the number of neighbors of a node.
652
653    This function returns the number of neighbors of a node `n0` in a triangulation created by [trmesh].
654
655    The number of neighbors also gives the order of the Voronoi polygon containing the point. Thus, a neighbor count of `6` means the node is contained in a `6`-sided Voronoi region.
656
657    This function is identical to the similarly named function in TRIPACK.
658
659    # Arguments
660
661    * `lpl` - Input. Pointer to the last neighbor of `n0`.
662    * `lptr[6 * (n - 2)]` - Input. Pointers associated with `list`.
663
664    # Returns
665
666    The number of neighbors of `n0`.
667    */
668    #[link_name = "nbcnt_"]
669    pub fn nbcnt(lpl: *const c_int, lptr: *const c_int) -> c_int;
670
671    /**
672    Returns the nearest node to a given point.
673
674    Given a point `p` on the surface of the unit sphere and a Delaunay triangulation created by [trmesh], this function returns the index of the nearest triangulation node to `p`.
675
676    The algorithm consists of implicitly adding `p` to the triangulation, finding the nearest neighbor to `p`, and implicitly deleting `p` from the triangulation. Thus, it is based on the fact that, if `p` is a node in a Delaunay triangulation, the nearest node to `p` is a neighbor of `p`.
677
678    For large values of `n`, this procedure will be faster than the naive approach of computing the distance from `p` to every node.
679
680    Note that the number of candidates for [nearnd] (neighbors of `p`) is limited to `lmax` defined in the arguments below.
681
682    # Arguments
683
684    * `p[3]` - Input. The Cartesian coordinates of the point `p` to be located relative to the
685    * triangulation. It is assumed that `p[0]**2 + p[1]**2 + p[2]**2 = 1`, that is, that the
686      point lies on the unit sphere.
687    * `ist` - Input. The index of the node at which the search is to begin. The search time
688    * depends on the proximity of this node to `p`. If no good candidate is known, any value
689      between `1` and `n` will do.
690    * `n` - Input. The number of nodes in the triangulation. `3 <= n`.
691    * `x[n]`, `y[n]`, `z[n]` - Input. The Cartesian coordinates of the nodes.
692    * `list[6 * (n - 2)]`, `lptr[6 * (n - 2)]`, `lend[n]` - Input. The data structure defining
693      the triangulation, created by [trmesh].
694    * `al` - Output. The arc length between `p` and node [nearnd]. Because both points are on
695      the unit sphere, this is also the angular separation in radians.
696
697    # Returns
698    The index of the nearest node to `p`. Will be `0` if `n < 3` or the triangulation data structure is invalid.
699    */
700    #[link_name = "nearnd_"]
701    pub fn nearnd(
702        p: *const c_double,
703        ist: *const c_int,
704        n: *const c_int,
705        x: *const c_double,
706        y: *const c_double,
707        z: *const c_double,
708        list: *const c_int,
709        lptr: *const c_int,
710        lend: *const c_int,
711        al: *mut c_double,
712    ) -> c_int;
713
714    /**
715    Optimizes the quadrilateral portion of a triangulation.
716
717    Given a set of `na` triangulation arcs, this subroutine optimizes the portion of the
718    triangulation consisting of the quadrilaterals (pairs of adjacent triangles) which have the arcs
719    as diagonals by applying the circumcircle test and appropriate swaps to the arcs.
720
721    An iteration consists of applying the swap test and swaps to all `na` arcs in the order in which
722    they are stored. The iteration is repeated until no swap occurs or `nit` iterations have
723    been performed. The bound on the number of iterations may be necessary to prevent an infinite
724    loop caused by cycling (reversing the effect of a previous swap) due to floating point
725    inaccuracy when four or more nodes are nearly cocircular.
726
727    # Arguments
728
729    * `x[*]`, `y[*]`, `z[*]` - Input. The nodal coordinates.
730    * `na` - Input. The number of arcs in the set. `na >= 0`.
731    * `list[6 * (n - 2)]`, `lptr[6 * (n - 2)]`, `lend[n]` - Input/output. The data structure
732      defining the triangulation, created by [trmesh]. On output, updated to reflect the swaps.
733    * `nit` - Input/output. On input, the maximum number of iterations to be performed. `nit = 4 *
734    na` should be sufficient. `nit >= 1`. On output, the number of iterations performed.
735    * `iwk[2][na]` - Input/output. The nodal indexes of the arc endpoints (paris of endpoints are
736      stored in columns). On output, endpoint indexes of the new set of arcs reflecting the swaps.
737    * `ier` - Output. Error indicator:
738      * `0`, if no errors were encountered.
739      * `1`, if a swap occurred on the last of `maxit` iterations, where `maxit` is the value of
740        `nit` on input. The new set of arcs is not necessarily optimal in this case.
741      * `2`, if `na < 0` or `nit < 1` on input
742      * `3`, if `iwk[2][i]` is not a neighbor of `iwk[1][i]` for some `i` in the range `1` to `na`. A
743        swap may have occurred in this case.
744      * `4`, if a zero pointer was returned by subroutine [swap].
745    */
746    #[link_name = "optim_"]
747    pub fn optim(
748        x: *const c_double,
749        y: *const c_double,
750        z: *const c_double,
751        na: *const c_int,
752        list: *mut c_int,
753        lptr: *mut c_int,
754        lend: *mut c_int,
755        nit: *mut c_int,
756        iwk: *mut c_int,
757        ier: *mut c_int,
758    );
759
760    /**
761    Normalizes each R83 in an R83Vec to have unit norm.
762
763    # Arguments
764
765    * `n` - Input. The number of nodes in the triangulation.
766    * `x[n]`, `y[n]`, `z[n]` - Input/output. On input, the unnormalized coordinates of the
767      triangulation. On output, the normalized coordinates of the triangulation (unit vectors).
768     */
769    #[link_name = "r83vec_normalize_"]
770    pub fn r83vec_normalize(n: *const c_int, x: *mut c_double, y: *mut c_double, z: *mut c_double);
771
772    /**
773    Converts from Cartesian to spherical coordinates (latitude, longitude, radius).
774
775    # Arguments
776    * `px`, `py`, `pz` - Input. The coordinates of `p`
777    * `plat` - Output. The latitude of `p` in the range `-PI/2` to `PI/2`, or `0` if `pnrm = 0`.
778    * `plon` - Output. The longitude of `p` in the range `-PI` to `PI`, or `0` if `p` lies on the
779      Z-axis.
780    * `pnrm` - Output. The magnitude (Euclidean norm) of `p`.
781
782    # Safety
783    - All pointers must be valid and properly aligned
784    */
785    #[link_name = "scoord_"]
786    pub fn scoord(
787        px: *const c_double,
788        py: *const c_double,
789        pz: *const c_double,
790        plat: *mut c_double,
791        plon: *mut c_double,
792        pnrm: *mut c_double,
793    );
794
795    /**
796    Replaces the diagonal arc of a quadrilateral with the other diagonal.
797
798    Given a triangulation of a set of points on the unit sphere, this subroutine replaces a diagonal
799    arc in a strictly convex quadrilateral (defined by a pair of adjacent triangles) with the other
800    diagonal. Equivalently, a pair of adjacent triangles is replaced by another pair having the same
801    union.
802
803    # Arguments
804
805    * `in1`, `in2`, `io1`, `io2` - Input. Nodal indexes of the vertices of the quadrilateral. `io1 -
806    io2` is replaced by `in1 - in2`. (`io1`, `io2`, `in1`) and (`io2`, `io1`, `in2`) must be
807      triangles on input.
808
809    * `list[6 * (n - 2)]`, `lptr[6 * (n - 2)]`, `lend[n]` - Input/output. The data structure
810      defining the triangulation, created by [trmesh]. On output, updated with the swap; triangles
811      (`io1`, `io2`, `in1`) and (`io2`, `io1`, `in2`) are replaced by (`in1`, `in2`, `io2`) and (`in2`, `in1`, `io1`) unless `lp21 = 0`.
812    * `lp21` - Output. Index of `in1` as a neighbor of `in2` after the swap is performed unless `in1` and `in2` are adjacent on input, in which case `lp21 = 0`.
813    */
814    #[link_name = "swap_"]
815    pub fn swap(
816        in1: *const c_int,
817        in2: *const c_int,
818        io1: *const c_int,
819        io2: *const c_int,
820        list: *mut c_int,
821        lptr: *mut c_int,
822        lend: *mut c_int,
823        lp21: *mut c_int,
824    );
825
826    /**
827    Decides whether to replace a diagonal arc by the other in a quadrilateral.
828
829    The decision will be to swap (`swptst = true`) if and only if `n4` lies above the plane (in the half-space
830    not containing the origin) defined by (`n1`, `n2`, `n3`), or equivalently, if the projection of
831    `n4` onto this plane is interior to the circumcircle of (`n1`, `n2`, `n3`). The decision will be
832    for no swap if the quadrilateral is not strictly convex.
833
834    # Arguments
835
836    * `n1`, `n2`, `n3`, `n4` - Input. The indexes of the four nodes defining the quadrilateral with
837      `n1` adjacent to `n2`, and (`n1`, `n2`, `n3`) in counterclockwise order. The arc connecting `n1`
838      to `n2` should be replaced by an arc connection `n3` to `n4` if `swptst = true`. Refer to
839      subroutine [swap].
840    * `x[n]`, `y[n]`, `z[n]` - Input. The coordinates of the nodes.
841
842    # Returns
843
844    True if and only if the arc connecting `n1` and `n2` should be swapped for an arc connecting `n3` and `n4`.
845    */
846    #[link_name = "swptst_"]
847    pub fn swptst(
848        n1: *const c_int,
849        n2: *const c_int,
850        n3: *const c_int,
851        n4: *const c_int,
852        x: *const c_double,
853        y: *const c_double,
854        z: *const c_double,
855    ) -> bool;
856
857    /**
858    Transform spherical coordinates into Cartesian coordinates
859    on the unit sphere for input to [trmesh].
860
861    Storage for X and Y may coincide with storage for `rlat` and `rlon` if the latter need not be saved.
862
863    # Arguments
864    * `n` - Input. The number of nodes (points on the unit sphere) whose coordinates are to be transformed
865    * `rlat` - Input. The latitudes of the nodes in radians.
866    * `rlon` - Input. The longitudes of the nodes in radians.
867    * `x`, `y`, and `z` - Output. The coordinates in the range `-1` to `1`. `x[i]**2 + y[i]**2 + z[i]**2 = 1` for `i` = 1 to `n`
868    # Safety
869    - All pointers must be valid and properly aligned
870    - Arrays `rlat`, `rlon`, `x`, `y`, `z` must have length >= `*n`
871    - Arrays `x`, `y`, `z` must not overlap with `rlat`, `rlon`
872    - `n` must be > 0
873    */
874    #[link_name = "trans_"]
875    pub fn trans(
876        n: *const c_int,
877        rlat: *const c_double,
878        rlon: *const c_double,
879        x: *mut c_double,
880        y: *mut c_double,
881        z: *mut c_double,
882    );
883
884    /**
885    Locates a point relative to a triangulation.
886
887    This subroutine locates a point `p` relative to a triangulation created by [trmesh]. If `p` is contained
888    in a triangle, the three vertex indexes and barycentric coordinates are returned. Otherwise, the
889    indexes of the visible boundary nodes are returned.
890
891    # Arguments
892    * `nst` - Input. The index of a node at which [trfind] begins its search. Search time depends on
893      the proximity of this node to `p`.
894    * `p[3]` - Input. The x, y, and z coordinates (in that order) of the point `p` to be located.
895    * `n` - Input. The number of nodes in the triangulation. `3 <= n`.
896    * `x[n]`, `y[n]`, `z[n]`, the coordinates of the triangulation nodes (unit vectors).
897    * `list[6 * (n - 2)]`, `lptr[6 * (n - 2)]`, `lend[n]` - Input. The data structure defining the
898      triangulation, created by [trmesh].
899      `b1`, `b2`, `b3` - Output. The unnormalized barycentric coordinates of the central projection of
900      `p` onto the underlying planar triangle if `p` is in the convex hull of the nodes. These
901      parameters are not altered if `i1 = 0`.
902    * `i1`, `i2`, `i3` - Output. The counterclockwise-ordered vertex indexes of a triangle
903      containing `p` if `p` is contained in a triangle. If `p` is not in the convex hull of
904      the nodes, `i1` and `i2` are the rightmost and leftmost (boundary) nodes that are visible from
905      `p`, and `i3 = 0`. (If all boundary nodes are visible from `p`, then `i1` and `i2` coincide.)
906      `i1 = i2 = i3 = 0` if `p` and all of the nodes are coplanar (lie on a common great circle).
907    */
908    #[link_name = "trfind_"]
909    pub fn trfind(
910        nst: *const c_int,
911        p: *const c_double,
912        n: *const c_int,
913        x: *const c_double,
914        y: *const c_double,
915        z: *const c_double,
916        list: *const c_int,
917        lptr: *const c_int,
918        lend: *const c_int,
919        b1: *mut c_double,
920        b2: *mut c_double,
921        b3: *mut c_double,
922        i1: *mut c_int,
923        i2: *mut c_int,
924        i3: *mut c_int,
925    );
926
927    /**
928    Convert a triangulation data structure into a triangle list.
929
930    # Arguments
931    * `n` - Input. The number of nodes in the triangulation. `3 <= n`.
932    * `list`, `lptr`, `lend` - Input. Linked list data structure defining the triangulation.
933    * `nrow` - Input. The number of rows (entries per triangle) reserved for the triangle list `ltri`. The
934      value must be 6 if only the vertex indexes and neighboring triangle indexes are to be stored, or
935      if arc indexes are also to be assigned and stored. Refer to `ltri`.
936    * `nt` - Output. The number of triangles in the triangulation unless `ier != 0`, which
937      in case `nt = 0`. `nt = 2n - nb - 2` if `nb >= 3` or `2n-4` if `nb = 0`, where `nb` is the
938      number of boundary nodes.
939    * `ltri` - Output. The second dimension of `ltri` must be at least `nt`, where `nt` will be at
940      most `2*n - 4`. The `j`-th column contains the vertex nodal indexes (first three rows),
941      neighboring triangle indxes (second three rows), and, if `nrow = 9`, arc indexes (last three
942      rows) associated with triangle `j` for `j = 1, ..., nt`. The vertices are ordered counterclockwise
943      with the first vertex taken to be the one with smallest index. Thus `ltri[2][j]` and `ltri[3][j]` are larger than `ltri[1, j]` and index adjacent neighbors of node `ltri[1][j]`. For `i = 1, 2, 3`, `ltri[i + 3][j]` and `ltri[i + 6][j]` index the triangle and arc, respectively, which are opposite (not shared by) node `ltri[i][j]`, with `ltri[i + 3, j] = 0` if `ltri[i + 6][j]` indexes a boundary arc. Vertex indexes range from `1` to `n`, triangle indexes from `0` to `nt`, and, if included, arc indexes from `1` to `na`, where `na = 3n - nb - 3` if `nb >= 3` or `3n - 6` if `nb = 0`. The triangles are ordered on first (smallest) vertex indexes.
944    * ier - Output. Error indicator.
945      * `0`, if no errors were encountered.
946      * `1`, if `n` or `nrow` is outside its valid range on input.
947      * `2`, if the triangulation data structure (`list`, `lptr`, `lend`) is invalid. Note, however, that
948        these arrays are not completely tested for validity.
949    */
950    #[link_name = "trlist_"]
951    pub fn trlist(
952        n: *const c_int,
953        list: *const c_int,
954        lptr: *const c_int,
955        lend: *const c_int,
956        nrow: *const c_int,
957        nt: *mut c_int,
958        ltri: *mut c_int,
959        ier: *mut c_int,
960    );
961
962    /**
963    Converts a triangulation data structure to a triangle list.
964
965    This subroutine converts a triangulation data structure from the linked list created by [trmesh] to a triangle list.
966
967    It is a version of [trlist] for the special case where the triangle list should only include the nodes that define each triangle.
968
969    # Arguments
970
971    * `n` - Input. The number of nodes in the triangulation.
972    * `list[6 * (n - 2)]`, `lptr[6 * (n - 2)]`, `lend[n]` - Input. The linked list data
973      structure defining the triangulation. Refer to [trmesh].
974    * `nt` - Output. The number of triangles in the triangulation unless `ier != 0`, in which
975      case `nt = 0`. `nt = 2n - nb - 2` if `nb >= 3` or `2n - 4` if `nb = 0`, where `nb` is the number of boundary nodes.
976    * `ltri[3][*]` - Output. The second dimension of `ltri` must be at least `nt`, where `nt`
977      will be at most `2 * n - 4`. The `j`-th column contains the vertex nodal indexes
978      associated with triangle `j` for `j = 1, ..., nt`. The vertices are ordered counterclockwise with the first vertex taken to be the one with the smallest index. Thus, `ltri[2][j]` and `ltri[3][j]` are larger than `ltri[1][j]` and index adjacent neighbors of node `ltri[1][j]`. The triangles are ordered on first (smallest) vertex indexes.
979    * `ier` - Output. Error indicator:
980      * `0`, if no errors were encountered.
981      * `1`, if `n` is outside its valid range on input.
982      * `2`, if the triangulation data structure (`list`, `lptr`, `lend`) is invalid. Note, however, that these arrays are not completely tested for validity.
983    */
984    #[link_name = "trlist2_"]
985    pub fn trlist2(
986        n: *const c_int,
987        list: *const c_int,
988        lptr: *const c_int,
989        lend: *const c_int,
990        nt: *mut c_int,
991        ltri: *mut c_int,
992        ier: *mut c_int,
993    );
994
995    /**
996     Creates a Delaunay triangulation on the unit sphere.
997
998     The Delaunay triangulation is defined as a set of (spherical) triangles with the following five
999     properties:
1000     1. The triangle vertices are nodes.
1001     2. No triangle contains a node other than its vertices.
1002     3. The interiors of the triangles are pairwise disjoint.
1003     4. The union of triangles is the convex hull of the set of nodes (the smallest convex set that
1004        contains the nodes). If the nodes are not contained in a single hemisphere, their convex hull
1005        is the entire sphere and there are no boundary nodes. Otherwise, there are at least three
1006        boundary nodes.
1007     5. The interior of the circumcircle of each triangle contains no node.
1008
1009     The first four properties define a triangulation, and the last property results in a
1010     triangulation which is as close as possible to equiangular in a certain sense and which is
1011     uniquely defined unless four or more nodes lie in a common plane. This property makes the
1012     triangulation well-suited for solving closest-point problems and for triangle based
1013     interpolation.
1014
1015     Provided the nodes are randomly ordered, the algorithm has expected time complexity O(N*log(N))
1016     for most nodal distributions. Note, however, that the complexity may be as high as O(N**2) if,
1017     for example, the nodes are ordered on increasing latitude.
1018
1019     Spherical coordinates (latitude and longitude) may be converted to Cartesian coordinates by
1020     [trans].
1021
1022     The following is a list of the software package modules which a user may wish to call directly:
1023     * [addnod] - Updates the triangulation by appending a new node.
1024     * [areas] - Returns the area of a spherical triangle
1025     * [bnodes] - Returns an array containing the index of the boundary nodes (if any) in
1026       counterclockwise order. Counts of boundary nodes, triangles, and arcs are also returned.
1027     * [circum] - Returns the circumcenter of a spherical triangle.
1028     * [crlist] - Returns the set of triangle circumcenters (Voronoi vertices) and circumradii
1029       associated with a triangulation.
1030     * [delarc] - Deletes a boundary arc from a triangulation.
1031     * [edge] - Forces an arbitrary pair of nodes to be connected by an arc in the triangulation.
1032     * [getnp] - Determines the ordered sequence of L closest nodes to a given node, along with the
1033       associated distances.
1034     * [inside] - Locates a point relative to a polygon on the surface of the sphere.
1035     * [intrsc] - Returns the point of intersection between a pair of great circle arcs.
1036     * [jrand] - Generates a uniformly distributed pseudo-random integer.
1037     * [left] - Locates a point relative to a great circle
1038     * [nearnd] - Returns the index of the nearest node to an arbitrary point, along with its squared
1039       distance.
1040     * [scoord] - Converts a point from Cartesian coordinates to spherical coordinates.
1041     * [trans] - Transforms spherical coordinates into Cartesian coordinates on the unit sphere for
1042       input to [trmesh]
1043     * [trlist] - Converts the triangulation data structure to a triangle list more suitable for use in
1044       a finite element code.
1045
1046     # Arguments
1047     * `n` - Input. The number of nodes in the triangulation. `3 <= n`.
1048     * `x[n]`, `y[n]`, `z[n]` - Input. The coordinates of distinct nodes. `(x[k], y[k], z[k])` is referred to as node `k`, and `k` is referred to as a nodal index. It is required that `x[k]**2 + y[k]**2 + z[k]**2 = 1` for all `k`. The first three nodes must not be collinear (lie on a common great circle).
1049     * `list` - Output. `6 * (n - 2)` nodal indexes which, along with `lptr`, `lend`, and `lnew`
1050       define the triangulation as a set of `n` adjacency lists; counterclockwise-ordered sequences of
1051       neighboring nodes such that the first and last neighbors of a boundary node are boundary nodes
1052       (the first neighbor of an interior node is arbitrary). In order to distinguish between interior
1053       and boundary nodes, the last neighbor of each boundary node is represented by the negative of
1054       its index.
1055    * `lptr` - Output. Set of pointers (`list` indexes) in one-to-one correspondence with the
1056      elements of `list`. `list[lptr[i]]` indexes the node which follows `list[i]` in cyclical
1057      counterclockwise order (the first neighbor follows the last neighbor).
1058    * `lend` - Output. `n` pointers to adjacency lists. `lend[k]` points to the last neighbor of
1059      node `k`. `list[lend[k]] < 0` if and only if `k` is a boundary node.
1060    * `lnew` - Output. Pointer to the first empty location in `list` and `lptr` (list length plus
1061      one). `list`, `lptr`, `lend` and `lnew` are not altered if `ier < 0`, and are incomplete if `0 <
1062     ier`.
1063    * `near` - Workspace. An array of `n` integers used to efficiently determine the nearest
1064      triangulation node to each unprocessed node for use by [addnod].
1065    * `next` - Workspace. An array of `n` integers used to efficiently determine the nearest triangulation node
1066      to each unprocessed node for use by [addnod].
1067    * `dist` - Workspace. An array of `n` floats used to efficiently determine the neareast
1068      triangulation node to each unprocessed node for use by [addnod].
1069    * `ier` - Output. An integer error indicator:
1070       * `0`, if no errors were encountered.
1071       * `-1`, if `n < 3` on input.
1072       * `-2`, if the first three nodes are collinear.
1073       * `L`, if nodes L and M coincide for some L < M. The data structure represents a triangulation
1074         of nodes 1 to M-1 in this case.
1075     */
1076    #[link_name = "trmesh_"]
1077    pub fn trmesh(
1078        n: *const c_int,
1079        x: *const c_double,
1080        y: *const c_double,
1081        z: *const c_double,
1082        list: *mut c_int,
1083        lptr: *mut c_int,
1084        lend: *mut c_int,
1085        lnew: *mut c_int,
1086        near: *mut c_int,
1087        next: *mut c_int,
1088        dist: *mut c_double,
1089        ier: *mut c_int,
1090    );
1091
1092}
1093
1094#[cfg(test)]
1095mod test {
1096    use proptest::prelude::*;
1097    use rstest::{fixture, rstest};
1098
1099    use super::*;
1100    use std::{
1101        collections::HashSet,
1102        f64::consts::{FRAC_1_SQRT_2, FRAC_PI_2, FRAC_PI_4, PI, TAU},
1103    };
1104
1105    #[rstest]
1106    #[case(1, &[FRAC_PI_2], &[0.0], &[0.0], &[0.0], &[1.0])]
1107    #[case(1, &[-FRAC_PI_2], &[0.0], &[0.0], &[0.0], &[-1.0])]
1108    #[case(1, &[0.0], &[0.0], &[1.0], &[0.0], &[0.0])]
1109    #[case(1, &[0.0], &[FRAC_PI_2], &[0.0], &[1.0], &[0.0])]
1110    #[case(1, &[0.0], &[PI], &[-1.0], &[0.0], &[0.0])]
1111    #[case(1, &[0.0], &[-FRAC_PI_2], &[0.0], &[-1.0], &[0.0])]
1112    #[case(1, &[std::f64::consts::FRAC_PI_4], &[0.0], &[FRAC_1_SQRT_2], &[0.0], &[FRAC_1_SQRT_2])]
1113    #[case(1, &[FRAC_PI_4], &[FRAC_PI_4], &[0.5], &[0.5], &[FRAC_1_SQRT_2])]
1114    #[case(3, &[FRAC_PI_2, -FRAC_PI_2, 0.0], &[0.0, 0.0, 0.0], &[0.0, 0.0, 1.0], &[0.0, 0.0, 0.0], &[1.0, -1.0, 0.0])]
1115    fn test_trans(
1116        #[case] n: i32,
1117        #[case] rlat: &[f64],
1118        #[case] rlon: &[f64],
1119        #[case] expected_x: &[f64],
1120        #[case] expected_y: &[f64],
1121        #[case] expected_z: &[f64],
1122    ) {
1123        let mut x = vec![0.0; n as usize];
1124        let mut y = vec![0.0; n as usize];
1125        let mut z = vec![0.0; n as usize];
1126
1127        unsafe {
1128            trans(
1129                &raw const n,
1130                rlat.as_ptr(),
1131                rlon.as_ptr(),
1132                x.as_mut_ptr(),
1133                y.as_mut_ptr(),
1134                z.as_mut_ptr(),
1135            );
1136        }
1137
1138        for i in 0..n as usize {
1139            assert!((x[i] - expected_x[i]).abs() < f64::EPSILON);
1140            assert!((y[i] - expected_y[i]).abs() < f64::EPSILON);
1141            assert!((z[i] - expected_z[i]).abs() < f64::EPSILON);
1142        }
1143    }
1144
1145    #[rstest]
1146    #[case(&[1.0, 0.0, 0.0], &[0.0, 1.0, 0.0], &[0.0, 0.0, 1.0], FRAC_PI_2)]
1147    #[case(&[1.0, 0.0, 0.0], &[0.0, -1.0, 0.0], &[0.0, 0.0, 1.0], FRAC_PI_2)]
1148    #[case(&[-1.0, 0.0, 0.0], &[0.0, 1.0, 0.0], &[0.0, 0.0, -1.0], FRAC_PI_2)]
1149    #[case(&[1.0, 0.0, 0.0], &[0.0, 1.0, 0.0], &[0.0, 0.0, -1.0], FRAC_PI_2)]
1150    #[case(&[1.0, 0.0, 0.0], &[1.0, 0.0, 0.0], &[1.0, 0.0, 0.0], 0.0)]
1151    #[case(&[1.0, 0.0, 0.0], &[0.0, 1.0, 0.0], &[-1.0, 0.0, 0.0], 0.0)]
1152    #[case(&[FRAC_1_SQRT_2, FRAC_1_SQRT_2, 0.0], &[FRAC_1_SQRT_2, -FRAC_1_SQRT_2, 0.0], &[0.0, 0.0, 1.0], FRAC_PI_2)]
1153    fn test_areas(
1154        #[case] v1: &[f64; 3],
1155        #[case] v2: &[f64; 3],
1156        #[case] v3: &[f64; 3],
1157        #[case] expected_area: f64,
1158    ) {
1159        let area = unsafe { areas(v1.as_ptr(), v2.as_ptr(), v3.as_ptr()) };
1160        assert!(
1161            (area - expected_area).abs() < f64::EPSILON,
1162            "expected {expected_area} got {area}"
1163        );
1164    }
1165
1166    #[rstest]
1167    #[case(1.0, 0.0, 0.0, 0.0, 0.0, 1.0)]
1168    #[case(0.0, 1.0, 0.0, 0.0, FRAC_PI_2, 1.0)]
1169    #[case(-1.0, 0.0, 0.0, 0.0, PI, 1.0)]
1170    #[case(0.0, -1.0, 0.0, 0.0, -FRAC_PI_2, 1.0)]
1171    #[case(0.0, 0.0, 1.0, FRAC_PI_2, 0.0, 1.0)]
1172    #[case(0.0, 0.0, -1.0, -FRAC_PI_2, 0.0, 1.0)]
1173    #[case(FRAC_1_SQRT_2, FRAC_1_SQRT_2, 0.0, 0.0, FRAC_PI_4, 1.0)]
1174    #[case(0.5, 0.5, FRAC_1_SQRT_2, FRAC_PI_4, FRAC_PI_4, 1.0)]
1175    #[case(2.0, 0.0, 0.0, 0.0, 0.0, 2.0)]
1176    #[case(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)]
1177    fn test_scoord(
1178        #[case] px: f64,
1179        #[case] py: f64,
1180        #[case] pz: f64,
1181        #[case] expected_plat: f64,
1182        #[case] expected_plon: f64,
1183        #[case] expected_pnrm: f64,
1184    ) {
1185        let mut plat = 0.0;
1186        let mut plon = 0.0;
1187        let mut pnrm = 0.0;
1188
1189        unsafe {
1190            scoord(
1191                &raw const px,
1192                &raw const py,
1193                &raw const pz,
1194                &raw mut plat,
1195                &raw mut plon,
1196                &raw mut pnrm,
1197            );
1198        };
1199
1200        assert!((expected_plat - plat).abs() < f64::EPSILON);
1201        assert!((expected_plon - plon).abs() < f64::EPSILON);
1202        assert!((expected_pnrm - pnrm).abs() < f64::EPSILON);
1203
1204        let n = 1;
1205        let mut x = 0.0;
1206        let mut y = 0.0;
1207        let mut z = 0.0;
1208
1209        unsafe {
1210            trans(
1211                &raw const n,
1212                &raw const plat,
1213                &raw const plon,
1214                &raw mut x,
1215                &raw mut y,
1216                &raw mut z,
1217            );
1218        };
1219        if pnrm == 0.0 {
1220            return;
1221        }
1222
1223        let norm = (x * x + y * y + z * z).sqrt();
1224
1225        assert!(
1226            (x / norm - px / pnrm).abs() < f64::EPSILON,
1227            "expected: {px} got: {x}"
1228        );
1229        assert!(
1230            (y / norm - py / pnrm).abs() < f64::EPSILON,
1231            "expected: {py} got: {y}"
1232        );
1233        assert!(
1234            (z / norm - pz / pnrm).abs() < f64::EPSILON,
1235            "expected: {pz} got: {z}"
1236        );
1237    }
1238
1239    #[rstest]
1240    #[case(2, &[1.0, 0.0], &[0.0, 1.0], &[0.0, 0.0], -1)]
1241    fn test_trmesh(
1242        #[case] n: i32,
1243        #[case] x_in: &[f64],
1244        #[case] y_in: &[f64],
1245        #[case] z_in: &[f64],
1246        #[case] expected_ier: i32,
1247    ) {
1248        let mut x = Vec::with_capacity(n as usize);
1249        let mut y = Vec::with_capacity(n as usize);
1250        let mut z = Vec::with_capacity(n as usize);
1251
1252        for i in 0..n as usize {
1253            let norm = (x_in[i].powi(2) + y_in[i].powi(2) + z_in[i].powi(2)).sqrt();
1254            x.push(x_in[i] / norm);
1255            y.push(y_in[i] / norm);
1256            z.push(z_in[i] / norm);
1257        }
1258
1259        let list_size = if n >= 3 { 6 * (n - 2) } else { 0 } as usize;
1260
1261        let mut list = vec![0i32; list_size];
1262        let mut lptr = vec![0i32; list_size];
1263        let mut lend = vec![0i32; n as usize];
1264        let mut lnew = 0i32;
1265
1266        let mut near = vec![0i32; n as usize];
1267        let mut next = vec![0i32; n as usize];
1268        let mut dist = vec![0.0f64; n as usize];
1269        let mut ier = 0i32;
1270
1271        unsafe {
1272            trmesh(
1273                &raw const n,
1274                x.as_ptr(),
1275                y.as_ptr(),
1276                z.as_ptr(),
1277                list.as_mut_ptr(),
1278                lptr.as_mut_ptr(),
1279                lend.as_mut_ptr(),
1280                &raw mut lnew,
1281                near.as_mut_ptr(),
1282                next.as_mut_ptr(),
1283                dist.as_mut_ptr(),
1284                &raw mut ier,
1285            );
1286        }
1287
1288        assert_eq!(ier, expected_ier);
1289
1290        if expected_ier != 0 {
1291            return;
1292        }
1293
1294        assert!(lnew > 0, "lnew should be positive");
1295        assert!(lnew as usize <= list_size, "lnew should be within bounds");
1296
1297        for i in 0..n as usize {
1298            let lend_val = lend[i];
1299            assert!(lend_val > 0, "lend[{i}] should be positive");
1300        }
1301    }
1302
1303    #[fixture]
1304    fn tetrahedron() -> (Vec<f64>, Vec<f64>, Vec<f64>) {
1305        let tx = vec![
1306            0.816_496_580_9,
1307            -0.408_248_290_50,
1308            -0.408_248_290_590_5,
1309            0.0,
1310        ];
1311        let ty = vec![0.0, FRAC_1_SQRT_2, -FRAC_1_SQRT_2, 0.0];
1312        let tz = vec![
1313            -0.577_350_269_2,
1314            0.577_350_269_2,
1315            0.577_350_269_2,
1316            -0.577_350_269_2,
1317        ];
1318        (tx, ty, tz)
1319    }
1320
1321    #[fixture]
1322    fn octahedron() -> (Vec<f64>, Vec<f64>, Vec<f64>) {
1323        let ox = vec![1.0, -1.0, 0.0, 0.0, 0.0, 0.0];
1324        let oy = vec![0.0, 0.0, 1.0, -1.0, 0.0, 0.0];
1325        let oz = vec![0.0, 0.0, 0.0, 0.0, 1.0, -1.0];
1326
1327        (ox, oy, oz)
1328    }
1329
1330    #[rstest]
1331    #[case(4, 6, 4)]
1332    #[case(4, 9, 4)]
1333    #[case(6, 6, 8)]
1334    #[case(6, 9, 8)]
1335    #[case(5, 6, 0)]
1336    fn test_trlist(
1337        #[case] n: i32,
1338        #[case] nrow: i32,
1339        #[case] expected_nt: i32,
1340        tetrahedron: (Vec<f64>, Vec<f64>, Vec<f64>),
1341        octahedron: (Vec<f64>, Vec<f64>, Vec<f64>),
1342    ) {
1343        let x = if n == 4 { tetrahedron.0 } else { octahedron.0 };
1344        let y = if n == 4 { tetrahedron.1 } else { octahedron.1 };
1345        let z = if n == 4 { tetrahedron.2 } else { octahedron.2 };
1346
1347        let list_size = (6 * (n - 2)) as usize;
1348        let mut list = vec![0i32; list_size];
1349        let mut lptr = vec![0i32; list_size];
1350        let mut lend = vec![0i32; n as usize];
1351        let mut lnew = 0i32;
1352
1353        let mut near = vec![0i32; n as usize];
1354        let mut next = vec![0i32; n as usize];
1355        let mut dist = vec![0.0f64; n as usize];
1356        let mut ier = 0i32;
1357
1358        unsafe {
1359            trmesh(
1360                &raw const n,
1361                x.as_ptr(),
1362                y.as_ptr(),
1363                z.as_ptr(),
1364                list.as_mut_ptr(),
1365                lptr.as_mut_ptr(),
1366                lend.as_mut_ptr(),
1367                &raw mut lnew,
1368                near.as_mut_ptr(),
1369                next.as_mut_ptr(),
1370                dist.as_mut_ptr(),
1371                &raw mut ier,
1372            );
1373        };
1374
1375        assert_eq!(ier, 0, "trmesh failed");
1376
1377        let max_triangles = (2 * n - 4) as usize;
1378        let mut ltri = vec![0i32; (nrow as usize) * max_triangles];
1379        let mut nt = 0i32;
1380        let mut ier2 = 0i32;
1381
1382        unsafe {
1383            trlist(
1384                &raw const n,
1385                list.as_ptr(),
1386                lptr.as_ptr(),
1387                lend.as_ptr(),
1388                &raw const nrow,
1389                &raw mut nt,
1390                ltri.as_mut_ptr(),
1391                &raw mut ier2,
1392            );
1393        }
1394
1395        if nrow == 6 || nrow == 9 {
1396            assert_eq!(ier2, 0, "trlist should succeed");
1397            assert_eq!(nt, expected_nt, "triangle count mismatch");
1398            let v1 = ltri[0];
1399            assert!(v1 >= 1 && v1 <= n, "invalid vertex index");
1400            return;
1401        }
1402
1403        assert_eq!(ier2, 1, "should fail with inalid nrow");
1404        assert_eq!(nt, 0, "nt should be 0 on error");
1405    }
1406
1407    fn fibonacci_sphere(n: usize) -> (Vec<f64>, Vec<f64>, Vec<f64>) {
1408        let phi = (1.0 + 5.0_f64.sqrt()) / 2.0;
1409        let mut x_points = Vec::with_capacity(n);
1410        let mut y_points = Vec::with_capacity(n);
1411        let mut z_points = Vec::with_capacity(n);
1412
1413        for i in 0..n {
1414            let y = 1.0 - (i as f64 / (n as f64 - 1.0)) * 2.0;
1415            let radius = (1.0 - y * y).sqrt();
1416            let theta = 2.0 * std::f64::consts::PI * i as f64 * phi;
1417            let x = radius * theta.cos();
1418            let z = radius * theta.sin();
1419
1420            x_points.push(x);
1421            y_points.push(y);
1422            z_points.push(z);
1423        }
1424
1425        (x_points, y_points, z_points)
1426    }
1427
1428    #[fixture]
1429    fn hemisphere_fixed() -> (Vec<f64>, Vec<f64>, Vec<f64>) {
1430        let x = vec![1.0, 0.0, -1.0, 0.0, 0.0];
1431        let y = vec![0.0, 1.0, 0.0, -1.0, 0.0];
1432        let z = vec![0.0, 0.0, 0.0, 0.0, 1.0];
1433
1434        (x, y, z)
1435    }
1436
1437    fn hemisphere(n: usize) -> (Vec<f64>, Vec<f64>, Vec<f64>) {
1438        let (x, y, z) = fibonacci_sphere(n * 2);
1439
1440        let mut hx = Vec::with_capacity(n);
1441        let mut hy = Vec::with_capacity(n);
1442        let mut hz = Vec::with_capacity(n);
1443
1444        for i in 0..x.len() {
1445            if z[i] >= 0.0 && hx.len() < n {
1446                hx.push(x[i]);
1447                hy.push(y[i]);
1448                hz.push(z[i]);
1449            }
1450        }
1451
1452        (hx, hy, hz)
1453    }
1454
1455    #[rstest]
1456    #[case(4, &[], 0, 6, 4)]
1457    #[case(6, &[], 0, 12, 8)]
1458    #[case(5, &[1, 2, 3, 4], 4, 9, 4)]
1459    fn test_bnodes(
1460        #[case] n: i32,
1461        #[case] expected_boundary: &[i32],
1462        #[case] expected_nb: i32,
1463        #[case] expected_na: i32,
1464        #[case] expected_nt: i32,
1465        tetrahedron: (Vec<f64>, Vec<f64>, Vec<f64>),
1466        hemisphere_fixed: (Vec<f64>, Vec<f64>, Vec<f64>),
1467    ) {
1468        let (x, y, z) = if n == 4 {
1469            tetrahedron
1470        } else if n == 5 {
1471            hemisphere_fixed
1472        } else {
1473            fibonacci_sphere(n as usize)
1474        };
1475
1476        let list_size = (6 * (n - 2)) as usize;
1477        let mut list = vec![0i32; list_size];
1478        let mut lptr = vec![0i32; list_size];
1479        let mut lend = vec![0i32; n as usize];
1480        let mut lnew = 0i32;
1481
1482        let mut near = vec![0i32; n as usize];
1483        let mut next = vec![0i32; n as usize];
1484        let mut dist = vec![0.0f64; n as usize];
1485        let mut ier = 0i32;
1486
1487        unsafe {
1488            trmesh(
1489                &raw const n,
1490                x.as_ptr(),
1491                y.as_ptr(),
1492                z.as_ptr(),
1493                list.as_mut_ptr(),
1494                lptr.as_mut_ptr(),
1495                lend.as_mut_ptr(),
1496                &raw mut lnew,
1497                near.as_mut_ptr(),
1498                next.as_mut_ptr(),
1499                dist.as_mut_ptr(),
1500                &raw mut ier,
1501            );
1502        };
1503
1504        assert_eq!(ier, 0, "trmesh failed");
1505
1506        let mut nodes = vec![0i32; n as usize];
1507        let mut nb = 0i32;
1508        let mut na = 0i32;
1509        let mut nt = 0i32;
1510
1511        unsafe {
1512            bnodes(
1513                &raw const n,
1514                list.as_ptr(),
1515                lptr.as_ptr(),
1516                lend.as_ptr(),
1517                nodes.as_mut_ptr(),
1518                &raw mut nb,
1519                &raw mut na,
1520                &raw mut nt,
1521            );
1522        };
1523
1524        assert_eq!(nb, expected_nb, "boundary node count mismatch");
1525        assert_eq!(na, expected_na, "arc count mismatch");
1526        assert_eq!(nt, expected_nt, "triangle count mismatch");
1527
1528        if nb > 0 {
1529            for i in 0..nb as usize {
1530                assert_eq!(nodes[i], expected_boundary[i], "boundary node {i} mismatch");
1531            }
1532        }
1533    }
1534
1535    fn create_triangulation(
1536        n: i32,
1537        x: &[f64],
1538        y: &[f64],
1539        z: &[f64],
1540    ) -> (Vec<i32>, Vec<i32>, Vec<i32>, i32) {
1541        let list_size = (6 * (n - 2)) as usize;
1542        let mut list = vec![0i32; list_size];
1543        let mut lptr = vec![0i32; list_size];
1544        let mut lend = vec![0i32; n as usize];
1545        let mut lnew = 0i32;
1546
1547        let mut near = vec![0i32; n as usize];
1548        let mut next = vec![0i32; n as usize];
1549        let mut dist = vec![0.0f64; n as usize];
1550        let mut ier = 0i32;
1551
1552        unsafe {
1553            trmesh(
1554                &raw const n,
1555                x.as_ptr(),
1556                y.as_ptr(),
1557                z.as_ptr(),
1558                list.as_mut_ptr(),
1559                lptr.as_mut_ptr(),
1560                lend.as_mut_ptr(),
1561                &raw mut lnew,
1562                near.as_mut_ptr(),
1563                next.as_mut_ptr(),
1564                dist.as_mut_ptr(),
1565                &raw mut ier,
1566            );
1567        };
1568
1569        assert_eq!(ier, 0, "trmesh failed");
1570
1571        (list, lptr, lend, lnew)
1572    }
1573
1574    proptest! {
1575        #[test]
1576        fn trfind_locates_any_interior_point(px in -1.0f64..1.0, py in -1.0f64..1.0, pz in -1.0f64..1.0, n in 6..20) {
1577            let norm = (px * px + py * py + pz * pz).sqrt();
1578            if norm < f64::EPSILON {
1579                return Ok(());
1580            }
1581            let p = [px / norm, py / norm, pz / norm];
1582            let (x, y, z) = fibonacci_sphere(n as usize);
1583            let (list, lptr, lend, _) = create_triangulation(n, &x, &y, &z);
1584
1585            let nst = 1;
1586            let mut b1 = 0.0;
1587            let mut b2 = 0.0;
1588            let mut b3 = 0.0;
1589            let mut i1 = 0i32;
1590            let mut i2 = 0i32;
1591            let mut i3 = 0i32;
1592
1593            unsafe {
1594                trfind(
1595                    &raw const nst,
1596                    p.as_ptr(),
1597                    &raw const n,
1598                    x.as_ptr(),
1599                    y.as_ptr(),
1600                    z.as_ptr(),
1601                    list.as_ptr(),
1602                    lptr.as_ptr(),
1603                    lend.as_ptr(),
1604                    &raw mut b1,
1605                    &raw mut b2,
1606                    &raw mut b3,
1607                    &raw mut i1,
1608                    &raw mut i2,
1609                    &raw mut i3,
1610                );
1611            };
1612
1613            prop_assert!(i3 > 0, "i3 should be > 0");
1614            prop_assert!(i1 >= 1 && i1 <= n, "i1 should be a valid node index");
1615            prop_assert!(i2 >= 1 && i2 <= n, "i2 should be a valid node index");
1616            prop_assert!(i3 >= 1 && i3 <= n, "i3 should be a valid node index");
1617            prop_assert!(b1 > 0.0 && b2 > 0.0 && b3 > 0.0, "barycentric coords should be positive for an interior point");
1618        }
1619    }
1620
1621    fn unit_vector() -> impl Strategy<Value = [f64; 3]> {
1622        (-1.0f64..1.0, -1.0f64..1.0, -1.0f64..1.0).prop_map(|(x, y, z)| {
1623            let norm = (x * x + y * y + z * z).sqrt();
1624            if norm < f64::EPSILON {
1625                [1.0, 0.0, 0.0]
1626            } else {
1627                [x / norm, y / norm, z / norm]
1628            }
1629        })
1630    }
1631
1632    fn spherical_distance(a: &[f64], b: &[f64]) -> f64 {
1633        (a[0] * b[0] + a[1] * b[1] + a[2] * b[2])
1634            .clamp(-1.0, 1.0)
1635            .acos()
1636    }
1637
1638    fn normalize(v: &[f64]) -> [f64; 3] {
1639        let norm = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt();
1640        if norm < f64::EPSILON {
1641            [1.0, 0.0, 0.0]
1642        } else {
1643            [v[0] / norm, v[1] / norm, v[2] / norm]
1644        }
1645    }
1646
1647    proptest! {
1648        #[test]
1649        fn circum_equidistant(v1 in unit_vector(), v2 in unit_vector(), v3 in unit_vector()) {
1650            let mut c = [0.0; 3];
1651            let mut ier = 0i32;
1652
1653            unsafe {
1654                circum(
1655                    v1.as_ptr(),
1656                    v2.as_ptr(),
1657                    v3.as_ptr(),
1658                    c.as_mut_ptr(),
1659                    &raw mut ier
1660                );
1661            };
1662
1663            if ier != 0 {
1664                return Ok(());
1665            }
1666
1667            let d1 = spherical_distance(&c, &v1);
1668            let d2 = spherical_distance(&c, &v2);
1669            let d3 = spherical_distance(&c, &v3);
1670
1671            prop_assert!((d1 - d2).abs() < 1e-10, "d1={d1} d2={d2}");
1672            prop_assert!((d2 - d3).abs() < 1e-10, "d2={d2} d3={d3}");
1673            prop_assert!((d1 - d3).abs() < 1e-10, "d1={d1} d3={d3}");
1674        }
1675
1676        #[test]
1677        fn circum_on_unit_sphere(v1 in unit_vector(), v2 in unit_vector(), v3 in unit_vector()) {
1678            let mut c = [0.0; 3];
1679            let mut ier = 0i32;
1680
1681            unsafe {
1682                circum(
1683                    v1.as_ptr(),
1684                    v2.as_ptr(),
1685                    v3.as_ptr(),
1686                    c.as_mut_ptr(),
1687                    &raw mut ier
1688                );
1689            };
1690
1691            if ier != 0 {
1692                return Ok(());
1693            }
1694
1695            let norm = (c[0] * c[0] + c[1] * c[1] + c[2] * c[2]).sqrt();
1696            prop_assert!((norm - 1.0).abs() < 1e-10, "norm: {norm}");
1697        }
1698
1699        #[test]
1700        fn circum_collinear_error(value in 0f64..TAU) {
1701            let v1 = [value, 0.0, 0.0];
1702            let v2 = [value * 2.0, 0.0, 0.0];
1703            let v3 = [value * 3.0, 0.0, 0.0];
1704
1705            let mut c = [0.0; 3];
1706            let mut ier = 0i32;
1707
1708            unsafe {
1709                circum(
1710                    v1.as_ptr(),
1711                    v2.as_ptr(),
1712                    v3.as_ptr(),
1713                    c.as_mut_ptr(),
1714                    &raw mut ier
1715                );
1716            };
1717
1718            prop_assert!(ier == 1, "expected 1 got {ier}");
1719        }
1720    }
1721
1722    fn add_node_to_triangulation(
1723        nst: i32,
1724        k: i32,
1725        x: &[f64],
1726        y: &[f64],
1727        z: &[f64],
1728        list: &mut [i32],
1729        lptr: &mut [i32],
1730        lend: &mut [i32],
1731        lnew: &mut i32,
1732    ) -> i32 {
1733        let mut ier = 0i32;
1734        unsafe {
1735            addnod(
1736                &raw const nst,
1737                &raw const k,
1738                x.as_ptr(),
1739                y.as_ptr(),
1740                z.as_ptr(),
1741                list.as_mut_ptr(),
1742                lptr.as_mut_ptr(),
1743                lend.as_mut_ptr(),
1744                &raw mut *lnew,
1745                &raw mut ier,
1746            );
1747        };
1748
1749        ier
1750    }
1751
1752    #[rstest]
1753    fn test_addnod_adds_fourth_node_to_tetrahedron(tetrahedron: (Vec<f64>, Vec<f64>, Vec<f64>)) {
1754        let mut x = tetrahedron.0[0..3].to_vec();
1755        let mut y = tetrahedron.1[0..3].to_vec();
1756        let mut z = tetrahedron.2[0..3].to_vec();
1757
1758        let n = 3i32;
1759        let (mut list, mut lptr, mut lend, mut lnew) = create_triangulation(n, &x, &y, &z);
1760
1761        x.push(tetrahedron.0[3]);
1762        y.push(tetrahedron.1[3]);
1763        z.push(tetrahedron.2[3]);
1764
1765        let new_list_size = 6 * (n + 1 - 2) as usize;
1766        list.resize(new_list_size, 0);
1767        lptr.resize(new_list_size, 0);
1768        lend.resize(n as usize + 1, 0);
1769
1770        let k = n + 1;
1771        let nst = 1i32;
1772
1773        let add_ier = add_node_to_triangulation(
1774            nst, k, &x, &y, &z, &mut list, &mut lptr, &mut lend, &mut lnew,
1775        );
1776
1777        assert_eq!(add_ier, 0, "addnod should succeed");
1778        assert!(lnew > 0, "lnew should be positive after adding node");
1779    }
1780
1781    proptest! {
1782        #[test]
1783        fn test_arc_cosine(c in -10.0f64..10.0f64) {
1784            let acos = unsafe { arc_cosine(&raw const c) };
1785            prop_assert!((acos - c.clamp(-1.0, 1.0).acos()).abs() < f64::EPSILON);
1786        }
1787    }
1788
1789    fn scalar_triple_product(n0: &[f64], n1: &[f64], n2: &[f64]) -> f64 {
1790        let cross_x = n1[1] * n2[2] - n1[2] * n2[1];
1791        let cross_y = n1[2] * n2[0] - n1[0] * n2[2];
1792        let cross_z = n1[0] * n2[1] - n1[1] * n2[0];
1793
1794        n0[0] * cross_x + n0[1] * cross_y + n0[2] * cross_z
1795    }
1796
1797    fn is_left(n1: &[f64], n2: &[f64], n0: &[f64]) -> bool {
1798        unsafe {
1799            left(
1800                &raw const n1[0],
1801                &raw const n1[1],
1802                &raw const n1[2],
1803                &raw const n2[0],
1804                &raw const n2[1],
1805                &raw const n2[2],
1806                &raw const n0[0],
1807                &raw const n0[1],
1808                &raw const n0[2],
1809            )
1810        }
1811    }
1812
1813    proptest! {
1814        #[test]
1815        fn left_agrees_with_scalar_triple_product(n1 in unit_vector(), n2 in unit_vector(), n0 in unit_vector()) {
1816            let stp = scalar_triple_product(&n0, &n1, &n2);
1817            let result = is_left(&n1, &n2, &n0);
1818
1819            prop_assert_eq!(result, stp >= -f64::EPSILON);
1820        }
1821
1822        #[test]
1823        fn left_boundary_points_return_true(n1 in unit_vector(), n2 in unit_vector(), coeff1 in -1.0f64..1.0, coeff2 in -1.0f64..1.0) {
1824            if coeff1.abs() < f64::EPSILON || coeff2.abs() < f64::EPSILON {
1825                return Ok(());
1826            }
1827            let n0 = normalize(&[
1828                coeff1 * n1[0] + coeff2 * n2[0],
1829                coeff1 * n1[1] + coeff2 * n2[1],
1830                coeff1 * n1[2] + coeff2 * n2[2],
1831            ]);
1832
1833            let stp = scalar_triple_product(&n0, &n1, &n2);
1834            if stp.abs() < 1e-10 {
1835                return Ok(());
1836            }
1837            let result = is_left(&n1, &n2, &n0);
1838            prop_assert_eq!(result, stp >= 0.0);
1839        }
1840    }
1841
1842    fn should_swap(n1: i32, n2: i32, n3: i32, n4: i32, x: &[f64], y: &[f64], z: &[f64]) -> bool {
1843        unsafe {
1844            swptst(
1845                &raw const n1,
1846                &raw const n2,
1847                &raw const n3,
1848                &raw const n4,
1849                x.as_ptr(),
1850                y.as_ptr(),
1851                z.as_ptr(),
1852            )
1853        }
1854    }
1855
1856    fn swap_determinant(n1: &[f64], n2: &[f64], n3: &[f64], n4: &[f64]) -> f64 {
1857        let dx1 = n1[0] - n4[0];
1858        let dy1 = n1[1] - n4[1];
1859        let dz1 = n1[2] - n4[2];
1860
1861        let dx2 = n2[0] - n4[0];
1862        let dy2 = n2[1] - n4[1];
1863        let dz2 = n2[2] - n4[2];
1864
1865        let dx3 = n3[0] - n4[0];
1866        let dy3 = n3[1] - n4[1];
1867        let dz3 = n3[2] - n4[2];
1868
1869        dx3 * (dy2 * dz1 - dy1 * dz2) - dy3 * (dx2 * dz1 - dx1 * dz2)
1870            + dz3 * (dx2 * dy1 - dx1 * dy2)
1871    }
1872
1873    proptest! {
1874        #[test]
1875        fn test_swptst(
1876            n1 in unit_vector(),
1877            n2 in unit_vector(),
1878            n3 in unit_vector(),
1879            n4 in unit_vector()
1880        ) {
1881            let x = vec![n1[0], n2[0], n3[0], n4[0]];
1882            let y = vec![n1[1], n2[1], n3[1], n4[1]];
1883            let z = vec![n1[2], n2[2], n3[2], n4[2]];
1884
1885            let det = swap_determinant(&n1, &n2, &n3, &n4);
1886            let result = should_swap(1, 2, 3, 4, &x, &y, &z);
1887
1888            prop_assert_eq!(result, det > 0.0);
1889        }
1890    }
1891
1892    fn find_node_pointer(lpl: i32, nb: i32, list: &[i32], lptr: &[i32]) -> i32 {
1893        unsafe { lstptr(&raw const lpl, &raw const nb, list.as_ptr(), lptr.as_ptr()) }
1894    }
1895
1896    fn check_triangulation(n: i32, list: &[i32], lend: &[i32], lptr: &[i32]) {
1897        for node in 1..=n {
1898            let lpl = lend[(node - 1) as usize];
1899            assert!(lpl > 0, "Node {node} should have valid lend after edge");
1900
1901            let mut current = lpl;
1902            let mut count = 0;
1903            loop {
1904                let neighbor = list[(current - 1) as usize].abs();
1905                assert!(
1906                    neighbor >= 1 && neighbor <= n,
1907                    "Node {node} neighbor {neighbor} out of range"
1908                );
1909                count += 1;
1910                current = lptr[(current - 1) as usize];
1911                if current == lpl {
1912                    break;
1913                }
1914                assert!(count <= 6 * (n - 2), "Infinite loop in adjacency list");
1915            }
1916        }
1917    }
1918
1919    proptest! {
1920        fn lsptr_finds_all_neighbors_in_triangulation(n in 6..50i32) {
1921            let (x, y, z) = fibonacci_sphere(n as usize);
1922            let (list, lptr, lend, _) = create_triangulation(n, &x, &y, &z);
1923
1924            for node_idx in 1..=n {
1925                let lpl = lend[(node_idx - 1) as usize];
1926
1927                let mut current = lpl;
1928                let mut neighbor_count = 0;
1929
1930                loop {
1931                    let neighbor = list[(current - 1) as usize].abs();
1932
1933                    let found_ptr = find_node_pointer(lpl, neighbor, &list, &lptr);
1934
1935                    prop_assert!(found_ptr > 0 && found_ptr <= 6 * (n - 2), "Node {node_idx}: lstptr should return valid poiter for neighbor {neighbor}");
1936                    prop_assert_eq!(list[(found_ptr - 1) as usize].abs(), neighbor, "Node {}: lstptr should find correct neighbor {} at position {}", node_idx, neighbor, found_ptr);
1937
1938                    let mut traversal_ptr = lpl;
1939                    let mut found_in_traversal = false;
1940                    for _ in 0..(6 * (n - 2)) {
1941                        if traversal_ptr == found_ptr {
1942                            found_in_traversal = true;
1943                            break;
1944                        }
1945                        traversal_ptr = lptr[(traversal_ptr - 1) as usize];
1946                        if traversal_ptr == lpl {
1947                            break;
1948                        }
1949                    }
1950
1951                    prop_assert!(found_in_traversal, "Node {node_idx}: lstptr result {found_ptr} should be reachable from lpl {lpl}");
1952
1953                    neighbor_count += 1;
1954                    current = lptr[(current - 1) as usize];
1955
1956                    if current == lpl {
1957                        break;
1958                    }
1959                }
1960
1961                prop_assert!(neighbor_count >= 2, "Node {node_idx} should have at least 2 neighbors, found {neighbor_count}");
1962            }
1963        }
1964    }
1965
1966    proptest! {
1967         #[test]
1968         fn test_swap(n in 6..20i32) {
1969             let (x, y, z) = fibonacci_sphere(n as usize);
1970             let (mut list, mut lptr, mut lend, _) = create_triangulation(n, &x, &y, &z);
1971
1972             let nrow = 6i32;
1973             let max_triangles = (2 * n - 4) as usize;
1974             let mut ltri = vec![0i32; (nrow as usize) * max_triangles];
1975             let mut nt = 0i32;
1976    let mut ier = 0i32;
1977
1978             unsafe {
1979                 trlist(
1980                     &raw const n,
1981                     list.as_ptr(),
1982                     lptr.as_ptr(),
1983                     lend.as_ptr(),
1984                     &raw const nrow,
1985                     &raw mut nt,
1986                     ltri.as_mut_ptr(),
1987                     &raw mut ier,
1988                 );
1989             }
1990
1991             prop_assert_eq!(ier, 0, "trlist failed");
1992             prop_assert!(nt >= 2, "Need at least 2 triangles for a swap");
1993
1994
1995             'outer: for t1 in 0..(nt as usize) {
1996                 for t2 in (t1 + 1)..(nt as usize) {
1997                     let v1_0 = ltri[t1 * 6 + 0];
1998                     let v1_1 = ltri[t1 * 6 + 1];
1999                     let v1_2 = ltri[t1 * 6 + 2];
2000
2001                     let v2_0 = ltri[t2 * 6 + 0];
2002                     let v2_1 = ltri[t2 * 6 + 1];
2003                     let v2_2 = ltri[t2 * 6 + 2];
2004
2005                     let verts1 = [v1_0, v1_1, v1_2];
2006                     let verts2 = [v2_0, v2_1, v2_2];
2007
2008                     let mut shared = vec![];
2009                     let mut unique1 = None;
2010                     let mut unique2 = None;
2011
2012                     for &v in &verts1 {
2013                         if verts2.contains(&v) {
2014                             shared.push(v);
2015                         } else {
2016                             unique1 = Some(v);
2017                         }
2018                     }
2019
2020                     for &v in &verts2 {
2021                         if !verts1.contains(&v) {
2022                             unique2 = Some(v);
2023                         }
2024                     }
2025
2026                     if !(shared.len() == 2 && unique1.is_some() && unique2.is_some()) {
2027                         continue;
2028                     }
2029
2030                     let io1 = shared[0];
2031                     let io2 = shared[1];
2032                     let in1 = unique1.unwrap();
2033                     let in2 = unique2.unwrap();
2034
2035                     let lpl_io1 = lend[(io1 - 1) as usize];
2036                     let ptr_io2 = find_node_pointer(lpl_io1, io2, &list, &lptr);
2037
2038                     if !(ptr_io2 > 0 && list[(ptr_io2 - 1) as usize].abs() == io2) {
2039                         continue;
2040                     }
2041
2042                     let lpl_in1 = lend[(in1 - 1) as usize];
2043                     let ptr_in2_check = find_node_pointer(lpl_in1, in2, &list, &lptr);
2044                     if list[(ptr_in2_check - 1) as usize].abs() == in2 {
2045                         continue;
2046                     }
2047
2048                     if !should_swap(in1, in2, io1, io2, &x, &y, &z) {
2049                         continue;
2050                     }
2051
2052                     let mut lp21 = 0i32;
2053                     unsafe {
2054                         swap(
2055                             &raw const in1,
2056                             &raw const in2,
2057                             &raw const io1,
2058                             &raw const io2,
2059                             list.as_mut_ptr(),
2060                             lptr.as_mut_ptr(),
2061                             lend.as_mut_ptr(),
2062                             &raw mut lp21,
2063                         );
2064                     };
2065
2066                     prop_assert!(lp21 > 0, "Swap should succeed with lp21 > 0");
2067
2068                     let lpl_in2 = lend[(in2 - 1) as usize];
2069                     let ptr_in1 = find_node_pointer(lpl_in2, in1, &list, &lptr);
2070                     prop_assert!(ptr_in1 > 0, "IN1 should be neighbor of IN2 after swap");
2071                     prop_assert_eq!(list[(ptr_in1 - 1) as usize].abs(), in1);
2072
2073                     break 'outer;
2074                 }
2075             }
2076         }
2077     }
2078
2079    proptest! {
2080        #[test]
2081        fn test_optim(n in 8..30i32) {
2082            let (x, y, z) = fibonacci_sphere(n as usize);
2083            let (mut list, mut lptr, mut lend, _) = create_triangulation(n, &x, &y, &z);
2084
2085            let mut na = 0i32;
2086            let max_arcs = (3 * n) as usize;
2087            let mut iwk = vec![0i32; 2 * max_arcs];
2088
2089            for node in 1..n {
2090                let lpl = lend[(node - 1) as usize];
2091                let mut current = lpl;
2092
2093                loop {
2094                    let neighbor = list[(current - 1) as usize].abs();
2095                    if node < neighbor {
2096                        iwk[na as usize * 2] = node;
2097                        iwk[na as usize * 2 + 1] = neighbor;
2098                        na += 1;
2099                        if na >= 20 {
2100                            break;
2101                        }
2102                    }
2103
2104                    current = lptr[(current - 1) as usize];
2105                    if current == lpl {
2106                        break;
2107                    }
2108                }
2109
2110                if na >= 20 {
2111                    break;
2112                }
2113            }
2114
2115            prop_assert!(na > 0, "Should have at least one arc to optimize");
2116
2117            let mut nit = 100i32;
2118            let mut ier = 0i32;
2119
2120            unsafe {
2121                optim(
2122                    x.as_ptr(),
2123                    y.as_ptr(),
2124                    z.as_ptr(),
2125                    &raw const na,
2126                    list.as_mut_ptr(),
2127                    lptr.as_mut_ptr(),
2128                    lend.as_mut_ptr(),
2129                    &raw mut nit,
2130                    iwk.as_mut_ptr(),
2131                    &raw mut ier,
2132                );
2133            }
2134
2135            prop_assert!(ier == 0 || ier == 1, "optim failed");
2136
2137            check_triangulation(n, &list, &lend, &lptr);
2138
2139            if ier == 0 {
2140                prop_assert!(nit < 100, "Converged but used all iterations");
2141            }
2142        }
2143    }
2144
2145    proptest! {
2146        #[test]
2147        fn test_edge(n in 8..25i32) {
2148            let (x, y, z) = fibonacci_sphere(n as usize);
2149            let (mut list, mut lptr, mut lend, _) = create_triangulation(n, &x, &y, &z);
2150
2151            let mut in1 = 1i32;
2152            let mut in2 = 2i32;
2153            let mut found_non_adjacent = false;
2154
2155            'search: for i in 1..=n {
2156                let lpl = lend[(i - 1) as usize];
2157                let mut current = lpl;
2158                let mut neighbors = vec![];
2159
2160                loop {
2161                    let neighbor = list[(current - 1) as usize].abs();
2162                    neighbors.push(neighbor);
2163                    current = lptr[(current - 1) as usize];
2164                    if current == lpl {
2165                        break;
2166                    }
2167                }
2168
2169                for j in (i + 1)..=n {
2170                    if !neighbors.contains(&j) && i != j {
2171                        in1 = i;
2172                        in2 = j;
2173                        found_non_adjacent = true;
2174                        break 'search;
2175                    }
2176                }
2177            }
2178
2179            if !found_non_adjacent {
2180                return Ok(());
2181            }
2182
2183            let max_lwk = (n - 3) as usize;
2184            let mut lwk = max_lwk as i32;
2185            let mut iwk = vec![0i32; 2 * max_lwk];
2186            let mut ier = 0i32;
2187
2188            unsafe {
2189                edge(
2190                    &raw const in1,
2191                    &raw const in2,
2192                    x.as_ptr(),
2193                    y.as_ptr(),
2194                    z.as_ptr(),
2195                    &raw mut lwk,
2196                    iwk.as_mut_ptr(),
2197                    list.as_mut_ptr(),
2198                    lptr.as_mut_ptr(),
2199                    lend.as_mut_ptr(),
2200                    &raw mut ier
2201                );
2202            }
2203
2204            prop_assert!(ier == 0 || ier == 5, "edge failed");
2205
2206            let lpl_in1 = lend[(in1 - 1) as usize];
2207            let ptr_in2 = find_node_pointer(lpl_in1, in2, &list, &lptr);
2208            let is_adjacent = list[(ptr_in2 - 1) as usize].abs() == in2;
2209            prop_assert!(is_adjacent, "Nodes {in1} and {in2} should be adjacent after edge, lwk={lwk}");
2210
2211            check_triangulation(n, &list, &lend, &lptr);
2212
2213            prop_assert!(lwk >= 0 && lwk <= max_lwk as i32, "lwk should be in valid range [0, {max_lwk}], got {lwk}");
2214        }
2215    }
2216
2217    proptest! {
2218        #[test]
2219        fn test_insert(n in 6..15i32, insertion_point in 1..3usize) {
2220            let (x, y, z) = fibonacci_sphere(n as usize);
2221            let (mut list, mut lptr, lend, mut lnew) = create_triangulation(n, &x, &y, &z);
2222
2223            let lpl = lend[0];
2224
2225            let mut lp = lpl;
2226            for _ in 0..insertion_point {
2227                lp = lptr[(lp - 1) as usize];
2228            }
2229
2230            let k = n;
2231
2232            let mut current = lpl;
2233            let mut already_neighbor = false;
2234            loop {
2235                if list[(current - 1) as usize].abs() == k as i32 {
2236                    already_neighbor = true;
2237                    break;
2238                }
2239
2240                current = lptr[(current - 1) as usize];
2241                if current == lpl {
2242                    break;
2243                }
2244            }
2245
2246            if already_neighbor {
2247                return Ok(());
2248            }
2249
2250            let lnew_before = lnew;
2251
2252            let current_size = list.len();
2253            list.resize(current_size + 1, 0);
2254            lptr.resize(current_size + 1, 0);
2255
2256            unsafe {
2257                insert(
2258                    &raw const k,
2259                    &raw const lp,
2260                    list.as_mut_ptr(),
2261                    lptr.as_mut_ptr(),
2262                    &raw mut lnew
2263                );
2264            }
2265
2266            prop_assert_eq!(lnew, lnew_before + 1, "lnew should increment by 1");
2267            prop_assert_eq!(list[(lnew_before - 1) as usize], k, "k should be at position {}", lnew_before);
2268            let next_after_insert = lptr[(lp - 1) as usize];
2269            prop_assert_eq!(next_after_insert, lnew_before, "lp should now point ot new entry");
2270
2271            let next_of_new = lptr[(lnew_before - 1) as usize];
2272            prop_assert!(next_of_new > 0 && next_of_new <= list.len() as i32, "New entry should point to valid position");
2273        }
2274    }
2275
2276    #[test]
2277    fn test_bdyadd() {
2278        let (x, y, z) = hemisphere_fixed();
2279        let n_base = 5i32;
2280
2281        let (mut list, mut lptr, mut lend, mut lnew) = create_triangulation(n_base, &x, &y, &z);
2282
2283        let mut nodes = vec![0i32; n_base as usize];
2284        let mut nb = 0i32;
2285        let mut na = 0i32;
2286        let mut nt = 0i32;
2287
2288        unsafe {
2289            bnodes(
2290                &raw const n_base,
2291                list.as_ptr(),
2292                lptr.as_ptr(),
2293                lend.as_ptr(),
2294                nodes.as_mut_ptr(),
2295                &raw mut nb,
2296                &raw mut na,
2297                &raw mut nt,
2298            );
2299        }
2300
2301        if nb < 2 || lnew as usize >= list.len() {
2302            return;
2303        }
2304
2305        let i1 = nodes[0];
2306        let i2 = nodes[1];
2307        let kk = n_base + 1;
2308
2309        let new_size = list.len() + 10;
2310        list.resize(new_size, 0);
2311        lptr.resize(new_size, 0);
2312        lend.resize(n_base as usize + 1, 0);
2313
2314        let lnew_before = lnew;
2315
2316        unsafe {
2317            bdyadd(
2318                &raw const kk,
2319                &raw const i1,
2320                &raw const i2,
2321                list.as_mut_ptr(),
2322                lptr.as_mut_ptr(),
2323                lend.as_mut_ptr(),
2324                &raw mut lnew,
2325            );
2326        }
2327
2328        assert!(lnew > lnew_before, "bdyadd should increment lnew");
2329        assert!(
2330            lend[(kk - 1) as usize] > 0,
2331            "New node should have valid lend"
2332        );
2333
2334        let lpl_kk = lend[(kk - 1) as usize];
2335        let ptr_i1 = find_node_pointer(lpl_kk, i1, &list, &lptr);
2336        assert!(ptr_i1 > 0, "i1 should be a neighbor of kk");
2337        assert_eq!(list[(ptr_i1 - 1) as usize].abs(), i1);
2338
2339        let ptr_i2 = find_node_pointer(lpl_kk, i2, &list, &lptr);
2340        assert!(ptr_i2 > 0, "i2 should be a neighbor of kk");
2341        assert_eq!(list[(ptr_i2 - 1) as usize].abs(), i2);
2342
2343        check_triangulation(kk, &list, &lend, &lptr);
2344    }
2345
2346    proptest! {
2347        #[test]
2348        fn test_intadd(n in 6..20i32) {
2349            let (mut x, mut y, mut z) = fibonacci_sphere(n as usize);
2350            let (mut list, mut lptr, mut lend, mut lnew) = create_triangulation(n, &x, &y, &z);
2351
2352            let nrow = 6i32;
2353            let max_triangles = (2 * n - 4) as usize;
2354            let mut ltri = vec![0i32; (nrow as usize) * max_triangles];
2355            let mut nt = 0i32;
2356            let mut ier = 0i32;
2357
2358            unsafe {
2359                trlist(
2360                    &raw const n,
2361                    list.as_ptr(),
2362                    lptr.as_ptr(),
2363                    lend.as_ptr(),
2364                    &raw const nrow,
2365                    &raw mut nt,
2366                    ltri.as_mut_ptr(),
2367                    &raw mut ier,
2368                );
2369            }
2370
2371            if ier != 0 || nt < 1 {
2372                return Ok(());
2373            }
2374
2375            let i1 = ltri[0];
2376            let i2 = ltri[1];
2377            let i3 = ltri[2];
2378
2379            let x1 = x[(i1 - 1) as usize];
2380            let y1 = y[(i1 - 1) as usize];
2381            let z1 = z[(i1 - 1) as usize];
2382            let x2 = x[(i2 - 1) as usize];
2383            let y2 = y[(i2 - 1) as usize];
2384            let z2 = z[(i2 - 1) as usize];
2385            let x3 = x[(i3 - 1) as usize];
2386            let y3 = y[(i3 - 1) as usize];
2387            let z3 = z[(i3 - 1) as usize];
2388
2389            let px = 0.25 * x1 + 0.25 * x2 + 0.5 * x3;
2390            let py = 0.25 * y1 + 0.25 * y2 + 0.5 * y3;
2391            let pz = 0.25 * z1 + 0.25 * z2 + 0.5 * z3;
2392
2393            let [px, py, pz] = normalize(&[px, py, pz]);
2394            let p = [px, py, pz];
2395            let mut b1 = 0.0;
2396            let mut b2 = 0.0;
2397            let mut b3 = 0.0;
2398            let mut tf_i1 = 0i32;
2399            let mut tf_i2 = 0i32;
2400            let mut tf_i3 = 0i32;
2401            let nst = 1i32;
2402
2403            unsafe {
2404                trfind(
2405                    &raw const nst,
2406                    p.as_ptr(),
2407                    &raw const n,
2408                    x.as_ptr(),
2409                    y.as_ptr(),
2410                    z.as_ptr(),
2411                    list.as_ptr(),
2412                    lptr.as_ptr(),
2413                    lend.as_ptr(),
2414                    &raw mut b1,
2415                    &raw mut b2,
2416                    &raw mut b3,
2417                    &raw mut tf_i1,
2418                    &raw mut tf_i2,
2419                    &raw mut tf_i3,
2420                );
2421            }
2422
2423            if tf_i3 == 0 {
2424                return Ok(());
2425            }
2426
2427            let kk = n + 1;
2428            x.push(px);
2429            y.push(py);
2430            z.push(pz);
2431
2432            let new_list_size = list.len() + 6;
2433            list.resize(new_list_size, 0);
2434            lptr.resize(new_list_size, 0);
2435            lend.resize(kk as usize, 0);
2436
2437            let lnew_before = lnew;
2438
2439            unsafe {
2440                intadd(
2441                    &raw const kk,
2442                    &raw const tf_i1,
2443                    &raw const tf_i2,
2444                    &raw const tf_i3,
2445                    list.as_mut_ptr(),
2446                    lptr.as_mut_ptr(),
2447                    lend.as_mut_ptr(),
2448                    &raw mut lnew,
2449                );
2450            }
2451
2452            prop_assert_eq!(lnew, lnew_before + 6, "intadd should increment lnew by 3");
2453            prop_assert!(lend[(kk - 1) as usize] > 0, "New node should have valid lend");
2454
2455            for &vertex in &[tf_i1, tf_i2, tf_i3] {
2456                let lpl_kk = lend[(kk - 1) as usize];
2457                let ptr = find_node_pointer(lpl_kk, vertex, &list, &lptr);
2458                prop_assert!(ptr > 0, "Vertex {vertex} should be a neighbor of kk");
2459                prop_assert_eq!(list[(ptr - 1) as usize].abs(), vertex, "kk should have vertex {} as a neighbor", vertex);
2460            }
2461
2462            for &vertex in &[tf_i1, tf_i2, tf_i3] {
2463                let lpl_vertex = lend[(vertex - 1) as usize];
2464                let ptr = find_node_pointer(lpl_vertex, kk, &list, &lptr);
2465                prop_assert!(ptr > 0, "kk should be a neighbor of vertex {}", vertex);
2466                prop_assert_eq!(list[(ptr - 1) as usize].abs(), kk, "Vertex {} should have kk as a neighbor", vertex);
2467            }
2468
2469            check_triangulation(kk, &list, &lend, &lptr);
2470        }
2471    }
2472
2473    proptest! {
2474        #[test]
2475        fn test_covsph(n in 5..25i32) {
2476            let (x, y, z) = hemisphere(n as usize);
2477            let n_hemi = x.len() as i32;
2478
2479            if n_hemi < 4 {
2480                return Ok(());
2481            }
2482
2483            let (mut list, mut lptr, mut lend, mut lnew) = create_triangulation(n_hemi, &x, &y, &z);
2484
2485            let mut nodes = vec![0i32; n_hemi as usize];
2486            let mut nb = 0i32;
2487            let mut na = 0i32;
2488            let mut nt = 0i32;
2489
2490            unsafe {
2491                bnodes(
2492                    &raw const n_hemi,
2493                    list.as_ptr(),
2494                    lptr.as_ptr(),
2495                    lend.as_ptr(),
2496                    nodes.as_mut_ptr(),
2497                    &raw mut nb,
2498                    &raw mut na,
2499                    &raw mut nt,
2500                );
2501            }
2502
2503            if nb < 3 {
2504                return Ok(());
2505            }
2506
2507            let new_size = list.len() + (2 * nb as usize);
2508            list.resize(new_size, 0);
2509            lptr.resize(new_size, 0);
2510            lend.resize(n_hemi as usize + 1, 0);
2511
2512            let n0 = nodes[0];
2513            let kk = n_hemi + 1;
2514
2515            unsafe {
2516                covsph(
2517                    &raw const kk,
2518                    &raw const n0,
2519                    list.as_mut_ptr(),
2520                    lptr.as_mut_ptr(),
2521                    lend.as_mut_ptr(),
2522                    &raw mut lnew,
2523                );
2524            }
2525
2526            for i in 0..nb as usize {
2527                let lpl = lend[(nodes[i] - 1) as usize];
2528                prop_assert!(list[(lpl - 1) as usize] > 0);
2529            }
2530
2531            let lpl_kk = lend[(kk - 1) as usize];
2532            for i in 0..nb as usize {
2533                let ptr = find_node_pointer(lpl_kk, nodes[i], &list, &lptr);
2534                prop_assert!(ptr > 0);
2535            }
2536
2537            check_triangulation(kk, &list, &lend, &lptr);
2538        }
2539    }
2540
2541    proptest! {
2542        #[test]
2543        fn test_delnod(mut n in 5..25i32) {
2544            let (mut x, mut y, mut z) = fibonacci_sphere(n as usize);
2545            let (mut list, mut lptr, mut lend, mut lnew) = create_triangulation(n, &x, &y, &z);
2546
2547            let k = n / 2;
2548            let starting_n = n;
2549
2550            let lwk_max = n;
2551            let mut lwk = lwk_max;
2552            let mut iwk = vec![0i32; 2 * lwk_max as usize];
2553            let mut ier = 0i32;
2554
2555            unsafe {
2556                delnod(
2557                    &raw const k,
2558                    &raw mut n,
2559                    x.as_mut_ptr(),
2560                    y.as_mut_ptr(),
2561                    z.as_mut_ptr(),
2562                    list.as_mut_ptr(),
2563                    lptr.as_mut_ptr(),
2564                    lend.as_mut_ptr(),
2565                    &raw mut lnew,
2566                    &raw mut lwk,
2567                    iwk.as_mut_ptr(),
2568                    &raw mut ier,
2569                );
2570            }
2571
2572            prop_assert!(ier == 0 || ier == 6, "delnod failed");
2573            prop_assert_eq!(n, starting_n - 1, "n should be decremented by 1");
2574
2575            check_triangulation(n, &list, &lend, &lptr);
2576        }
2577    }
2578
2579    proptest! {
2580        #[test]
2581        fn test_jrand(n in 100..150, mut ix in 1..30000, mut iy in 1..30000, mut iz in 1..30000) {
2582            let x = unsafe { jrand(&raw const n, &raw mut ix, &raw mut iy, &raw mut iz) };
2583            prop_assert!(x <= n);
2584        }
2585    }
2586
2587    proptest! {
2588        #[test]
2589        fn test_nearnd(n in 5..25i32, p in unit_vector()) {
2590            let (x, y, z) = fibonacci_sphere(n as usize);
2591            let (list, lptr, lend, _) = create_triangulation(n, &x, &y, &z);
2592
2593            let mut al = 0.0;
2594            let ist = 1;
2595            let nearest = unsafe {
2596                nearnd(
2597                    p.as_ptr(),
2598                    &raw const ist,
2599                    &raw const n,
2600                    x.as_ptr(),
2601                    y.as_ptr(),
2602                    z.as_ptr(),
2603                    list.as_ptr(),
2604                    lptr.as_ptr(),
2605                    lend.as_ptr(),
2606                    &raw mut al,
2607                )
2608            };
2609
2610            let mut naive_nearest = 0;
2611            let mut naive_nearest_distance = f64::MAX;
2612            for i in 0..x.len() {
2613                let dist = ((p[0] - x[i]).powi(2) + (p[1] - y[i]).powi(2) + (p[2] - z[i]).powi(2)).sqrt();
2614                if dist < naive_nearest_distance {
2615                    naive_nearest = i;
2616                }
2617                naive_nearest_distance = naive_nearest_distance.min(dist);
2618            }
2619            prop_assert_eq!(nearest, naive_nearest as i32 + 1);
2620            prop_assert!((naive_nearest_distance - al).abs() < 0.10, "Incorrect al value: {al}, expected: {naive_nearest_distance}");
2621
2622        }
2623    }
2624
2625    proptest! {
2626        #[test]
2627        fn test_getnp(n in 10..25i32, l in 3..8i32) {
2628            let (x, y, z) = fibonacci_sphere(n as usize);
2629            let (list, lptr, mut lend, _) = create_triangulation(n, &x, &y, &z);
2630            let mut npts = vec![0i32; l as usize];
2631            npts[0] = n / 2;
2632            let mut distances = vec![0.0f64; l as usize];
2633            distances[0] = -1.0;
2634
2635            for i in 2..=l {
2636                let current_l = i;
2637                let mut df = 0.0f64;
2638                let mut ier = 0i32;
2639
2640                unsafe {
2641                    getnp(
2642                        x.as_ptr(),
2643                        y.as_ptr(),
2644                        z.as_ptr(),
2645                        list.as_ptr(),
2646                        lptr.as_ptr(),
2647                        lend.as_mut_ptr(),
2648                        &raw const current_l,
2649                        npts.as_mut_ptr(),
2650                        &raw mut df,
2651                        &raw mut ier,
2652                    );
2653                }
2654
2655                prop_assert_eq!(ier, 0, "getnp failed");
2656                prop_assert!(df >= -1.0 && df <= 1.0, "df: {df}");
2657                distances[(i - 1) as usize] = df;
2658                prop_assert!(df >= distances[(i - 2) as usize], "distance should be increasing: df={df}, prev={}", distances[(i - 2) as usize]);
2659            }
2660
2661            prop_assert_eq!(npts.into_iter().collect::<HashSet<_>>().len(), l as usize, "All {} nodes in sequence should be distinct", l);
2662        }
2663    }
2664
2665    proptest! {
2666        #[test]
2667        fn test_nbcnt(n in 6..30i32) {
2668            let (x, y, z) = fibonacci_sphere(n as usize);
2669            let (_, lptr, lend, _) = create_triangulation(n, &x, &y, &z);
2670
2671            for node_idx in 1..=n {
2672                let lpl = lend[(node_idx - 1) as usize];
2673                let count = unsafe { nbcnt(&raw const lpl, lptr.as_ptr()) };
2674                let mut manual_count = 0;
2675                let mut current = lpl;
2676                loop {
2677                    manual_count += 1;
2678                    current = lptr[(current - 1) as usize];
2679                    if current == lpl {
2680                        break;
2681                    }
2682                    prop_assert!(manual_count <= 6 * (n - 2), "infinite loop detected");
2683                }
2684                prop_assert_eq!(count, manual_count, "nbcnt shoudl match manula count for node {}", node_idx);
2685            }
2686        }
2687    }
2688
2689    proptest! {
2690        #[test]
2691        fn test_delnb(n in 6..25i32) {
2692            let (x, y, z) = fibonacci_sphere(n as usize);
2693            let (mut list, mut lptr, mut lend, mut lnew) = create_triangulation(n, &x, &y, &z);
2694
2695            let n0 = n / 2;
2696            let lpl_n0 = lend[(n0 - 1) as usize];
2697
2698            let first_neighbor_pos = lptr[(lpl_n0 - 1) as usize];
2699            let nb = list[(first_neighbor_pos - 1) as usize];
2700
2701            if nb < 1 || nb > n {
2702                return Ok(());
2703            }
2704
2705            let lnew_before = lnew;
2706            let mut lph = 0i32;
2707
2708            unsafe {
2709                delnb(
2710                    &raw const n0,
2711                    &raw const nb,
2712                    &raw const n,
2713                    list.as_mut_ptr(),
2714                    lptr.as_mut_ptr(),
2715                    lend.as_mut_ptr(),
2716                    &raw mut lnew,
2717                    &raw mut lph,
2718                );
2719            }
2720
2721            prop_assert!(lph > 0, "delnb failed");
2722            prop_assert!(lnew <= lnew_before, "lnew should decrease or stay the same, got {lnew} expected <= {lnew_before}");
2723
2724            check_triangulation(n, &list, &lend, &lptr);
2725        }
2726    }
2727
2728    proptest! {
2729        #[test]
2730        fn test_delarc(n in 5..25i32) {
2731            let (x, y, z) = hemisphere(n as usize);
2732            let (mut list, mut lptr, mut lend, mut lnew) = create_triangulation(n, &x, &y, &z);
2733
2734            let mut nodes = vec![0i32; n as usize];
2735            let mut nb = 0i32;
2736            let mut na = 0i32;
2737            let mut nt = 0i32;
2738
2739            unsafe {
2740                bnodes(
2741                    &raw const n,
2742                    list.as_ptr(),
2743                    lptr.as_ptr(),
2744                    lend.as_ptr(),
2745                    nodes.as_mut_ptr(),
2746                    &raw mut nb,
2747                    &raw mut na,
2748                    &raw mut nt,
2749                );
2750            }
2751
2752            if nb < 2 {
2753                return Ok(());
2754            }
2755
2756            let io1 = nodes[0];
2757            let io2 = nodes[1];
2758
2759            let lpl_io1 = lend[(io1 - 1) as usize];
2760            let ptr_io2 = find_node_pointer(lpl_io1, io2, &list, &lptr);
2761            let are_neighbors = list[(ptr_io2 - 1) as usize].abs() == io2;
2762
2763            if !are_neighbors {
2764                return Ok(());
2765            }
2766
2767            let mut ier = 0i32;
2768
2769            unsafe {
2770                delarc(
2771                    &raw const n,
2772                    &raw const io1,
2773                    &raw const io2,
2774                    list.as_mut_ptr(),
2775                    lptr.as_mut_ptr(),
2776                    lend.as_mut_ptr(),
2777                    &raw mut lnew,
2778                    &raw mut ier,
2779                );
2780            }
2781
2782            prop_assert_eq!(ier, 0, "delarc failed");
2783
2784            let lpl_io1_new = lend[(io1 - 1) as usize];
2785            let ptr_io2_new = find_node_pointer(lpl_io1_new, io2, &list, &lptr);
2786            let still_neighbors = list[(ptr_io2_new - 1) as usize].abs() == io2;
2787            prop_assert!(!still_neighbors, "io1 and io2 should no longer be neighbors");
2788
2789            check_triangulation(n, &list, &lend, &lptr);
2790        }
2791    }
2792
2793    proptest! {
2794        #[test]
2795        fn test_inside(n in 6..25i32, p in unit_vector()) {
2796            let (x, y, z) = fibonacci_sphere(n as usize);
2797
2798            let nv = 4i32;
2799            let listv = vec![1i32, 2, 3, 4];
2800
2801            let mut ier = 0i32;
2802
2803            let _ = unsafe {
2804                inside(
2805                    p.as_ptr(),
2806                    &raw const n,
2807                    x.as_ptr(),
2808                    y.as_ptr(),
2809                    z.as_ptr(),
2810                    &raw const nv,
2811                    listv.as_ptr(),
2812                    &raw mut ier,
2813                )
2814            };
2815
2816            prop_assert_eq!(ier, 0, "inside failed");
2817        }
2818    }
2819
2820    proptest! {
2821        #[test]
2822        fn test_crlist(n in 5i32..25i32) {
2823            let (x, y, z) = fibonacci_sphere(n as usize);
2824            let (list, lptr, lend, lnew) = create_triangulation(n, &x, &y, &z);
2825
2826            if lnew <= 0 {
2827                return Ok(());
2828            }
2829
2830            let nrow = 6i32;
2831            let max_triangles = (2 * n - 4) as usize;
2832            let mut ltri = vec![0i32; (nrow as usize) * max_triangles];
2833
2834
2835            let ncol = n;
2836            let max_centers = (2 * n - 4) as usize;
2837            let mut listc = vec![0i32; 3 * max_triangles];
2838            let mut nb = 0i32;
2839            let mut xc = vec![0.0f64; max_centers];
2840            let mut yc = vec![0.0f64; max_centers];
2841            let mut zc = vec![0.0f64; max_centers];
2842            let mut rc = vec![0.0f64; max_centers];
2843            let mut ier_crlist = 0i32;
2844            let mut lptr_copy = lptr.clone();
2845            let mut lnew_copy = lnew;
2846
2847            unsafe {
2848                crlist(
2849                    &raw const n,
2850                    &raw const ncol,
2851                    x.as_ptr(),
2852                    y.as_ptr(),
2853                    z.as_ptr(),
2854                    list.as_ptr(),
2855                    lend.as_ptr(),
2856                    lptr_copy.as_mut_ptr(),
2857                    &raw mut lnew_copy,
2858                    ltri.as_mut_ptr(),
2859                    listc.as_mut_ptr(),
2860                    &raw mut nb,
2861                    xc.as_mut_ptr(),
2862                    yc.as_mut_ptr(),
2863                    zc.as_mut_ptr(),
2864                    rc.as_mut_ptr(),
2865                    &raw mut ier_crlist,
2866                );
2867            };
2868
2869            if nb > 0 {
2870                return Ok(());
2871            }
2872
2873            prop_assert_eq!(ier_crlist, 0, "crlist failed");
2874
2875            let nt = 2 * n - 4;
2876            for i in 0..nt as usize {
2877                let norm_sq = xc[i] * xc[i] + yc[i] * yc[i] + zc[i] * zc[i];
2878                prop_assert!((norm_sq - 1.0).abs() < 1e-10, "circumcenter {i} should be a unit vector, norm_sq={norm_sq}");
2879
2880                let base = i * 6;
2881                let v1 = (ltri[base] - 1) as usize;
2882                let v2 = (ltri[base + 1] - 1) as usize;
2883                let v3 = (ltri[base + 2] - 1) as usize;
2884
2885                if v1 >= n as usize || v2 >= n as usize || v3 >= n as usize {
2886                    continue;
2887                }
2888
2889                let c = [xc[i], yc[i], zc[i]];
2890                let p1 = [x[v1], y[v1], z[v1]];
2891                let p2 = [x[v2], y[v2], z[v2]];
2892                let p3 = [x[v3], y[v3], z[v3]];
2893
2894                let d1 = spherical_distance(&c, &p1);
2895                let d2 = spherical_distance(&c, &p2);
2896                let d3 = spherical_distance(&c, &p3);
2897
2898                prop_assert!((d1 - d2).abs() < 1e-9 && (d2 - d3).abs() < 1e-9, "circumcenter {i} not equidistant: d1={d1}, d2={d2}, d3={d3}, rc={}", rc[i]);
2899                prop_assert!((rc[i] - d1).abs() < 1e-9, "circumradius mismatch for triangle {i}: rc={}, actual distance={d1}", rc[i]);
2900            }
2901        }
2902    }
2903
2904    fn cross_product(v1: &[f64; 3], v2: &[f64; 3]) -> [f64; 3] {
2905        [
2906            v1[1] * v2[2] - v1[2] * v2[1],
2907            v1[2] * v2[0] - v1[0] * v2[2],
2908            v1[0] * v2[1] - v1[1] * v2[0],
2909        ]
2910    }
2911
2912    fn norm(v: &[f64; 3]) -> f64 {
2913        (v[0].powi(2) + v[1].powi(2) + v[2].powi(2)).sqrt()
2914    }
2915
2916    fn dot(v1: &[f64; 3], v2: &[f64; 3]) -> f64 {
2917        v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]
2918    }
2919
2920    proptest! {
2921        #[test]
2922        fn test_intrsc(p1 in unit_vector(), p2 in unit_vector(), q in unit_vector()) {
2923            let c12 = cross_product(&p1, &p2);
2924            let norm_c12 = norm(&c12);
2925            if norm_c12 < f64::EPSILON {
2926                return Ok(());
2927            }
2928            let c12 = normalize(&c12);
2929
2930            let cn = cross_product(&p1, &q);
2931            let norm_cn = norm(&cn);
2932            if norm_cn < f64::EPSILON {
2933                return Ok(());
2934            }
2935
2936            let mut p = [0f64; 3];
2937            let mut ier = 0i32;
2938
2939            unsafe {
2940                intrsc(
2941                    p1.as_ptr(),
2942                    p2.as_ptr(),
2943                    cn.as_ptr(),
2944                    p.as_mut_ptr(),
2945                    &raw mut ier,
2946                );
2947            };
2948
2949            prop_assert_eq!(ier, 0, "intrsc failed");
2950
2951            let norm_p = norm(&p);
2952            prop_assert!((norm_p - 1.0).abs() < 1e-10, "intersection point should be on unit sphere, norm={norm_p}");
2953
2954            let dot_p_cn = dot(&p, &cn);
2955            prop_assert!(dot_p_cn.abs() < 1e-10, "intersection should be perpendicular to cn, dot={dot_p_cn}");
2956
2957            let dot_p_c12 = dot(&p, &c12);
2958            prop_assert!(dot_p_c12.abs() < 1e-10, "intersection should be perpendicular to c12 normal, dot={dot_p_c12}");
2959        }
2960    }
2961
2962    proptest! {
2963        #[test]
2964        fn test_r83vec_normalize(x in -10.0f64..10.0, y in -10.0f64..10.0, z in -10.0f64..10.0f64) {
2965            let expected_vector = normalize(&[x, y, z]);
2966            let mut xv = [x];
2967            let mut yv = [y];
2968            let mut zv = [z];
2969            let n = 1i32;
2970            unsafe {
2971                r83vec_normalize(
2972                    &raw const n,
2973                    xv.as_mut_ptr(),
2974                    yv.as_mut_ptr(),
2975                    zv.as_mut_ptr()
2976                );
2977            }
2978
2979            prop_assert!((xv[0] - expected_vector[0]).abs() < 1e-10, "normalize failed. Expected x={} got x={}", expected_vector[0], xv[0]);
2980            prop_assert!((yv[0] - expected_vector[1]).abs() < 1e-10, "normalize failed. Expected y={} got y={}", expected_vector[1], yv[0]);
2981            prop_assert!((zv[0] - expected_vector[2]).abs() < 1e-10, "normalize failed. Expected z={} got z={}", expected_vector[2], zv[0]);
2982        }
2983    }
2984
2985    proptest! {
2986        #[test]
2987        fn test_trlist2(n in 5i32..25i32) {
2988            let (x, y, z) = fibonacci_sphere(n as usize);
2989            let (list, lptr, lend, lnew) = create_triangulation(n, &x, &y, &z);
2990
2991            if lnew <= 0 {
2992                return Ok(());
2993            }
2994
2995            let max_triangles = (2 * n - 4) as usize;
2996            let mut ltri = vec![0i32; 3 * max_triangles];
2997            let mut nt = 0i32;
2998            let mut ier = 0i32;
2999
3000            unsafe {
3001                trlist2(
3002                    &raw const n,
3003                    list.as_ptr(),
3004                    lptr.as_ptr(),
3005                    lend.as_ptr(),
3006                    &raw mut nt,
3007                    ltri.as_mut_ptr(),
3008                    &raw mut ier,
3009                );
3010            }
3011
3012            prop_assert_eq!(ier, 0, "trlist2 failed");
3013            prop_assert!(nt > 0, "should have at least one triangle");
3014            prop_assert!(nt as usize <= max_triangles, "nt exceeds maximum");
3015
3016            let expected_nt = 2 * n - 4;
3017            prop_assert_eq!(nt, expected_nt, "triangle count mismatch: expected {} for n={}, got {}", expected_nt, n, nt);
3018
3019            for t in 0..nt as usize {
3020                let base = t * 3;
3021                let v1 = ltri[base];
3022                let v2 = ltri[base + 1];
3023                let v3 = ltri[base + 2];
3024
3025                prop_assert!(v1 >= 1 && v1 <= n, "invalid v1: {v1}");
3026                prop_assert!(v2 >= 1 && v2 <= n, "invalid v2: {v2}");
3027                prop_assert!(v3 >= 1 && v3 <= n, "invalid v3: {v3}");
3028
3029                prop_assert_ne!(v1, v2, "duplicate vertices in triangle");
3030                prop_assert_ne!(v2, v3, "duplicate vertices in triangle");
3031                prop_assert_ne!(v1, v3, "duplicate vertices in triangle");
3032
3033                prop_assert!(v1 < v2, "v1 should be smallest: v1={v1}, v2={v2}");
3034                prop_assert!(v1 < v3, "v1 should be smallest: v1={v1}, v3={v3}");
3035            }
3036
3037            for t in 1..nt as usize {
3038                let prev_base = (t - 1) * 3;
3039                let curr_base = t * 3;
3040                let prev_v1 = ltri[prev_base];
3041                let curr_v1 = ltri[curr_base];
3042
3043                prop_assert!(curr_v1 >= prev_v1, "triangles not ordered: {prev_v1} before {curr_v1}");
3044            }
3045        }
3046    }
3047}