Skip to content

Commit 13ff866

Browse files
ENH: Add the function tri_area_indexed(p, i)
1 parent cad58c1 commit 13ff866

File tree

6 files changed

+186
-18
lines changed

6 files changed

+186
-18
lines changed

README.md

+33-1
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ processed with the script in `ufunclab/tools/conv_template.py`.
110110
| [`cross2`](#cross2) | 2-d vector cross product (returns scalar) |
111111
| [`cross3`](#cross3) | 3-d vector cross product |
112112
| [`tri_area`](#tri_area) | Area of triangles in n-dimensional space |
113+
| [`tri_area_indexed`](#tri_area_indexed) | Area of triangles in n-dimensional space |
113114
| [`fillnan1d`](#fillnan1d) | Replace `nan` using linear interpolation |
114115
| [`linear_interp1d`](#linear_interp1d) | Linear interpolation, like `numpy.interp` |
115116
| [`backlash`](#backlash) | Backlash operator |
@@ -1530,7 +1531,7 @@ array([[[ -1., 0., 0.],
15301531

15311532
#### `tri_area`
15321533

1533-
`tri_area(p)` is a gufunc with signature `(3, n) - > ()`. It computes the
1534+
`tri_area(p)` is a gufunc with signature `(3, n) -> ()`. It computes the
15341535
area of a triangle defined by three points in n-dimensional space.
15351536

15361537
```
@@ -1550,6 +1551,37 @@ of two triangles in 4-dimensional space.
15501551
array([1.73205081, 0.70710678])
15511552
```
15521553

1554+
#### `tri_area_indexed`
1555+
1556+
`tri_area_indexed(p, i)` is a gufunc with signature `(m, n),(3) -> ()`.
1557+
It computes the area of a triangle defined by three points in n-dimensional
1558+
space. The first argument, `p`, is an array with shape `(m, n)` holding `m`
1559+
points in n-dimensional space. The second argument, `i`, is a 1-d array with
1560+
length three that holds indices into `p`. The core calculation is equivalent
1561+
to `tri_area(p[i])`.
1562+
1563+
```
1564+
>>> import numpy as np
1565+
>>> from ufunclab import tri_area_indexed, tri_area
1566+
1567+
>>> p = np.array([[0.0, 0.0, 0.0, 6.0],
1568+
[1.0, 2.0, 3.0, 6.0],
1569+
[0.0, 2.0, 2.0, 6.0],
1570+
[1.5, 1.0, 2.5, 2.0],
1571+
[4.0, 1.0, 0.0, 2.5],
1572+
[2.0, 1.0, 2.0, 2.5]])
1573+
>>> tri_area_indexed(p, [0, 2, 3])
1574+
6.224949798994367
1575+
>>> tri_area(p[[0, 2, 3]])
1576+
6.224949798994367
1577+
1578+
Compute the areas of several triangles formed from points in `p`.
1579+
Note that the last two are the same triangles.
1580+
1581+
>>> tri_area_indexed(p, [[0, 2, 3], [1, 3, 4], [3, 4, 5], [-1, -2, -3]])
1582+
array([6.2249498 , 7.46449931, 0.70710678, 0.70710678])
1583+
```
1584+
15531585
#### `fillnan1d`
15541586

15551587
`fillnan1d(x)` is a gufunc with signature `(i)->(i)`. It uses linear

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ requires = [
88

99
[project]
1010
name = 'ufunclab'
11-
version = '0.0.8.dev9'
11+
version = '0.0.8.dev10'
1212
description = 'NumPy ufuncs and utilities.'
1313
readme = 'README.md'
1414
requires-python = '>=3.9'

src/tri_area/define_cxx_gufunc_extmod.py

+64-6
Original file line numberDiff line numberDiff line change
@@ -36,22 +36,80 @@
3636
array([1.73205081, 0.70710678])
3737
"""
3838

39-
ufunc_src = UFuncSource(
39+
TRI_AREA_INDEXED_DOCSTRING = """\
40+
tri_area_indexed(p, i, /, ...)
41+
42+
Compute the area of the triangle formed by three points
43+
in n-dimensional space. `p` is an array of `m` points
44+
in n-dimensional space, and `i` is a 1-d array of
45+
three integers. These integers are indices into `p`.
46+
47+
Parameters
48+
----------
49+
p : array_like, shape (m, n)
50+
Input array of points.
51+
i : array_like, shape (3,)
52+
Indices into `i`.
53+
54+
Returns
55+
-------
56+
out : ndarray
57+
Area of the triangle formed by `p[i[0]]`, `p[i[1]]` and `p[i[2]]`.
58+
59+
Examples
60+
--------
61+
62+
>>> import numpy as np
63+
>>> from ufunclab import tri_area_indexed, tri_area
64+
65+
>>> p = np.array([[0.0, 0.0, 0.0, 6.0],
66+
[1.0, 2.0, 3.0, 6.0],
67+
[0.0, 2.0, 2.0, 6.0],
68+
[1.5, 1.0, 2.5, 2.0],
69+
[4.0, 1.0, 0.0, 2.5],
70+
[2.0, 1.0, 2.0, 2.5]])
71+
>>> tri_area_indexed(p, [0, 2, 3])
72+
6.224949798994367
73+
>>> tri_area(p[[0, 2, 3]])
74+
6.224949798994367
75+
76+
Compute the areas of several triangles formed from points in `p`.
77+
Note that the last two are the same triangles.
78+
79+
>>> tri_area_indexed(p, [[0, 2, 3], [1, 3, 4], [3, 4, 5], [-1, -2, -3]])
80+
array([6.2249498 , 7.46449931, 0.70710678, 0.70710678])
81+
82+
"""
83+
84+
ta_ufunc_src = UFuncSource(
4085
funcname='tri_area_core_calc',
4186
typesignatures=['f->f', 'd->d', 'g->g'],
4287
)
4388

44-
45-
ufunc = UFunc(
89+
ta_ufunc = UFunc(
4690
name='tri_area',
4791
header='tri_area_gufunc.h',
4892
docstring=TRI_AREA_DOCSTRING,
4993
signature='(3,n)->()',
50-
sources=[ufunc_src],
94+
sources=[ta_ufunc_src],
95+
)
96+
97+
tai_ufunc_src = UFuncSource(
98+
funcname='tri_area_indexed_core_calc',
99+
typesignatures=['fp->f', 'dp->d', 'gp->g'],
100+
)
101+
102+
tai_ufunc = UFunc(
103+
name='tri_area_indexed',
104+
header='tri_area_gufunc.h',
105+
docstring=TRI_AREA_INDEXED_DOCSTRING,
106+
signature='(m,n),(3)->()',
107+
sources=[tai_ufunc_src],
51108
)
52109

53110
extmod = UFuncExtMod(
54111
module='_tri_area',
55-
docstring="This extension module defines the gufunc 'tri_area'.",
56-
ufuncs=[ufunc],
112+
docstring=("This extension module defines the gufuncs 'tri_area' "
113+
"and 'tri_area_indexed'."),
114+
ufuncs=[ta_ufunc, tai_ufunc],
57115
)

src/tri_area/tri_area_gufunc.h

+69-8
Original file line numberDiff line numberDiff line change
@@ -27,26 +27,28 @@ static T norm_diff(npy_intp n, T *p_p, const npy_intp p_strides[2], npy_intp i1,
2727
}
2828

2929
//
30-
// `tri_area_core_calc`, the C++ core function
31-
// for the gufunc `tri_area` with signature '(3,n)->()'
32-
// for types ['f->f', 'd->d', 'g->g'].
30+
// This function is used by tri_area_core_calc and tri_area_indexed_core_calc.
3331
//
3432
template<typename T>
35-
static void tri_area_core_calc(
33+
static void
34+
tri_area_common_calc(
3635
npy_intp n, // core dimension n
37-
T *p_p, // pointer to first element of p, a strided 2-d array with shape (3, n)
36+
T *p_p, // pointer to first element of p, a strided 2-d array with shape (m, n)
3837
const npy_intp p_strides[2], // array of length 2 of strides (in bytes) of p
38+
npy_intp i0, // index of first point
39+
npy_intp i1, // index of second point
40+
npy_intp i2, // index of third point
3941
T *p_out // pointer to out
4042
)
4143
{
42-
T a = norm_diff(n, p_p, p_strides, 0, 1);
43-
T b = norm_diff(n, p_p, p_strides, 0, 2);
44+
T a = norm_diff(n, p_p, p_strides, i0, i1);
45+
T b = norm_diff(n, p_p, p_strides, i0, i2);
46+
T c = norm_diff(n, p_p, p_strides, i1, i2);
4447
if (b > a) {
4548
T t = a;
4649
a = b;
4750
b = t;
4851
}
49-
T c = norm_diff(n, p_p, p_strides, 1, 2);
5052
if (c > a) {
5153
T t = c;
5254
c = b;
@@ -69,4 +71,63 @@ static void tri_area_core_calc(
6971
*p_out = sqrt(f1*f2*f3*f4)/4;
7072
}
7173

74+
//
75+
// `tri_area_core_calc`, the C++ core function
76+
// for the gufunc `tri_area` with signature '(3,n)->()'
77+
// for types ['f->f', 'd->d', 'g->g'].
78+
//
79+
template<typename T>
80+
static void tri_area_core_calc(
81+
npy_intp n, // core dimension n
82+
T *p_p, // pointer to first element of p, a strided 2-d array with shape (3, n)
83+
const npy_intp p_strides[2], // array of length 2 of strides (in bytes) of p
84+
T *p_out // pointer to out
85+
)
86+
{
87+
tri_area_common_calc(n, p_p, p_strides, 0, 1, 2, p_out);
88+
}
89+
90+
91+
static inline
92+
npy_intp get_index(npy_intp *p_i, npy_intp i_stride, npy_intp index, npy_intp m)
93+
{
94+
npy_intp i = get(p_i, i_stride, index);
95+
if (i < 0) {
96+
i += m;
97+
}
98+
if (i < 0 || i >= m) {
99+
return -1;
100+
}
101+
return i;
102+
}
103+
104+
//
105+
// `tri_area_indexed_core_calc`, the C++ core function
106+
// for the gufunc `tri_area_indexed` with signature '(m, n),(3)->()'
107+
// for types ['fp->f', 'dp->d', 'gp->g'].
108+
//
109+
template<typename T>
110+
static void tri_area_indexed_core_calc(
111+
npy_intp m, // core dimension m
112+
npy_intp n, // core dimension n
113+
T *p_p, // pointer to first element of p, a strided
114+
// 2-d array with shape (m, n)
115+
const npy_intp p_strides[2], // array of length 2 of strides (in bytes) of p
116+
npy_intp *p_i, // pointer to first element of i, a strided 1-d array
117+
npy_intp i_stride, // stride of elements in i
118+
T *p_out // pointer to out
119+
)
120+
{
121+
npy_intp i0, i1, i2;
122+
i0 = get_index(p_i, i_stride, 0, m);
123+
i1 = get_index(p_i, i_stride, 1, m);
124+
i2 = get_index(p_i, i_stride, 2, m);
125+
if (i0 == -1 || i1 == -1 || i2 == -1) {
126+
// XXX Should probably raise IndexError instead of returning NAN.
127+
*p_out = NPY_NAN;
128+
return;
129+
}
130+
tri_area_common_calc(n, p_p, p_strides, i0, i1, i2, p_out);
131+
}
132+
72133
#endif

ufunclab/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from ._wjaccard import wjaccard
2727
from ._mad import mad, rmad, gini
2828
from ._vnorm import vnorm, rms, vdot
29-
from ._tri_area import tri_area
29+
from ._tri_area import tri_area, tri_area_indexed
3030
from ._backlash import backlash
3131
from ._fillnan1d import fillnan1d
3232
from ._linear_interp1d import linear_interp1d

ufunclab/tests/test_tri_area.py

+18-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import pytest
22
import numpy as np
33
from numpy.testing import assert_array_equal, assert_allclose
4-
from ufunclab import tri_area
4+
from ufunclab import tri_area, tri_area_indexed
55

66

77
@pytest.mark.parametrize('dt', [np.float32, np.float64, np.longdouble])
@@ -58,3 +58,20 @@ def test_4d():
5858
[1.0, 2.0, 5.0, 0.0]])
5959
a = tri_area(p)
6060
assert_allclose(a, 1.0, rtol=1e-14)
61+
62+
63+
def test_tri_area_indexed_basic():
64+
p = np.array([[2, 3, 0, 1],
65+
[0, 0, 0, 0],
66+
[1, 1, 3, 8],
67+
[4, 0, -1, 1],
68+
[3, 3, 3, 2]])
69+
a1 = tri_area(p[:3])
70+
a2 = tri_area(p[2:])
71+
a3 = tri_area(p[::2])
72+
a4 = tri_area(p[[-1, -2, 1]])
73+
a = tri_area_indexed(p, [[0, 1, 2],
74+
[2, 3, 4],
75+
[0, 2, 4],
76+
[-1, -2, 1]])
77+
assert_allclose(a, [a1, a2, a3, a4])

0 commit comments

Comments
 (0)