labelarray 0.1.0__tar.gz → 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {labelarray-0.1.0 → labelarray-0.1.1}/CHANGELOG.md +16 -0
- {labelarray-0.1.0/src/labelarray.egg-info → labelarray-0.1.1}/PKG-INFO +2 -2
- {labelarray-0.1.0 → labelarray-0.1.1}/README.md +1 -1
- labelarray-0.1.1/src/labelarray/__version__.py +1 -0
- {labelarray-0.1.0 → labelarray-0.1.1}/src/labelarray/sums.py +84 -29
- {labelarray-0.1.0 → labelarray-0.1.1/src/labelarray.egg-info}/PKG-INFO +2 -2
- {labelarray-0.1.0 → labelarray-0.1.1}/tests/test_sums.py +88 -2
- labelarray-0.1.0/src/labelarray/__version__.py +0 -1
- {labelarray-0.1.0 → labelarray-0.1.1}/LICENSE +0 -0
- {labelarray-0.1.0 → labelarray-0.1.1}/MANIFEST.in +0 -0
- {labelarray-0.1.0 → labelarray-0.1.1}/docs/releasing.md +0 -0
- {labelarray-0.1.0 → labelarray-0.1.1}/pyproject.toml +0 -0
- {labelarray-0.1.0 → labelarray-0.1.1}/scripts/check.sh +0 -0
- {labelarray-0.1.0 → labelarray-0.1.1}/setup.cfg +0 -0
- {labelarray-0.1.0 → labelarray-0.1.1}/src/labelarray/__init__.py +0 -0
- {labelarray-0.1.0 → labelarray-0.1.1}/src/labelarray/_compiled.py +0 -0
- {labelarray-0.1.0 → labelarray-0.1.1}/src/labelarray/align.py +0 -0
- {labelarray-0.1.0 → labelarray-0.1.1}/src/labelarray/array.py +0 -0
- {labelarray-0.1.0 → labelarray-0.1.1}/src/labelarray/collection.py +0 -0
- {labelarray-0.1.0 → labelarray-0.1.1}/src/labelarray/contract.py +0 -0
- {labelarray-0.1.0 → labelarray-0.1.1}/src/labelarray/errors.py +0 -0
- {labelarray-0.1.0 → labelarray-0.1.1}/src/labelarray/grouped.py +0 -0
- {labelarray-0.1.0 → labelarray-0.1.1}/src/labelarray/index.py +0 -0
- {labelarray-0.1.0 → labelarray-0.1.1}/src/labelarray/labels.py +0 -0
- {labelarray-0.1.0 → labelarray-0.1.1}/src/labelarray/py.typed +0 -0
- {labelarray-0.1.0 → labelarray-0.1.1}/src/labelarray/transforms.py +0 -0
- {labelarray-0.1.0 → labelarray-0.1.1}/src/labelarray.egg-info/SOURCES.txt +0 -0
- {labelarray-0.1.0 → labelarray-0.1.1}/src/labelarray.egg-info/dependency_links.txt +0 -0
- {labelarray-0.1.0 → labelarray-0.1.1}/src/labelarray.egg-info/requires.txt +0 -0
- {labelarray-0.1.0 → labelarray-0.1.1}/src/labelarray.egg-info/top_level.txt +0 -0
- {labelarray-0.1.0 → labelarray-0.1.1}/tests/test_array.py +0 -0
- {labelarray-0.1.0 → labelarray-0.1.1}/tests/test_collection.py +0 -0
- {labelarray-0.1.0 → labelarray-0.1.1}/tests/test_contraction.py +0 -0
- {labelarray-0.1.0 → labelarray-0.1.1}/tests/test_grouping.py +0 -0
- {labelarray-0.1.0 → labelarray-0.1.1}/tests/test_label_indexes.py +0 -0
- {labelarray-0.1.0 → labelarray-0.1.1}/tests/test_label_lookups.py +0 -0
- {labelarray-0.1.0 → labelarray-0.1.1}/tests/test_library_errors.py +0 -0
- {labelarray-0.1.0 → labelarray-0.1.1}/tests/test_readme.py +0 -0
- {labelarray-0.1.0 → labelarray-0.1.1}/tests/test_standalone.py +0 -0
- {labelarray-0.1.0 → labelarray-0.1.1}/tests/test_transforms.py +0 -0
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.1 — 2026-10-26
|
|
4
|
+
|
|
5
|
+
- Grouped float32 sums and means over many small groups are much faster:
|
|
6
|
+
200,000 values in 96,846 groups (voxels holding one to a few points) now
|
|
7
|
+
take 6 ms instead of 111. The exact mean of a few float32 values often
|
|
8
|
+
lies exactly halfway between two float32 values, where the error bound
|
|
9
|
+
cannot decide the rounding; about one group in ten went to an exact
|
|
10
|
+
path computed in Python fractions. Groups whose values span a narrow
|
|
11
|
+
range of exponents now have their exact sums computed as 64-bit integers
|
|
12
|
+
and rounded with NumPy (integer division kept to 27 or 28 bits with the
|
|
13
|
+
last bit set when anything is left over, then one rounding to float32),
|
|
14
|
+
so the Python path is left for groups spanning a wide range. Results are
|
|
15
|
+
unchanged, bit for bit: the tests compare them with exact rational
|
|
16
|
+
arithmetic, and the new code gave the same bits as 0.1.0 on two million
|
|
17
|
+
results.
|
|
18
|
+
|
|
3
19
|
## 0.1.0 — 2026-10-25
|
|
4
20
|
|
|
5
21
|
The first release on its own. labelarray was developed as the array layer
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: labelarray
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Labelled N-D arrays in pure NumPy, with exact label semantics and reproducible reductions
|
|
5
5
|
Author: Rigoberto Leyva Salmeron
|
|
6
6
|
License-Expression: MIT
|
|
@@ -46,7 +46,7 @@ It is deliberately small. What it offers that other array libraries do not:
|
|
|
46
46
|
3. **Collections.** Arrays sharing axes, with one label array per axis,
|
|
47
47
|
joined and aligned under those rules.
|
|
48
48
|
|
|
49
|
-
**Status:** version `0.1.
|
|
49
|
+
**Status:** version `0.1.1`; the API may still
|
|
50
50
|
change before 1.0. What changed in each release is in
|
|
51
51
|
[CHANGELOG.md](CHANGELOG.md).
|
|
52
52
|
|
|
@@ -18,7 +18,7 @@ It is deliberately small. What it offers that other array libraries do not:
|
|
|
18
18
|
3. **Collections.** Arrays sharing axes, with one label array per axis,
|
|
19
19
|
joined and aligned under those rules.
|
|
20
20
|
|
|
21
|
-
**Status:** version `0.1.
|
|
21
|
+
**Status:** version `0.1.1`; the API may still
|
|
22
22
|
change before 1.0. What changed in each release is in
|
|
23
23
|
[CHANGELOG.md](CHANGELOG.md).
|
|
24
24
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.1"
|
|
@@ -214,7 +214,14 @@ def _certify(total, magnitude, terms, counts, op, compensated):
|
|
|
214
214
|
|
|
215
215
|
|
|
216
216
|
def _exact(values, rows, chosen, groups, op, na_rm) -> np.ndarray:
|
|
217
|
-
"""Correctly rounded results for groups *chosen*, from exact sums of their values.
|
|
217
|
+
"""Correctly rounded results for groups *chosen*, from exact sums of their values.
|
|
218
|
+
|
|
219
|
+
Most such groups are small and hold values of nearby magnitudes (their
|
|
220
|
+
exact mean lies on a float32 tie, where the bound cannot decide): their
|
|
221
|
+
exact sums fit in 64-bit integers and are rounded with NumPy
|
|
222
|
+
(``_rounded``). Groups spanning a wider range of exponents are summed and
|
|
223
|
+
rounded in Python integers and fractions.
|
|
224
|
+
"""
|
|
218
225
|
if rows is None:
|
|
219
226
|
part, codes, count = values, np.zeros(len(values), dtype=np.intp), 1
|
|
220
227
|
else:
|
|
@@ -223,27 +230,35 @@ def _exact(values, rows, chosen, groups, op, na_rm) -> np.ndarray:
|
|
|
223
230
|
codes = remap[rows]
|
|
224
231
|
keep = codes >= 0
|
|
225
232
|
part, codes, count = values[keep], codes[keep], len(chosen)
|
|
226
|
-
|
|
227
|
-
out = np.
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
out[k] = _round32(totals[k])
|
|
233
|
-
else:
|
|
234
|
-
out[k] = _round32(totals[k] / present[k]) if present[k] else np.float32(np.nan)
|
|
233
|
+
buckets, low, present, nan, plus, minus = _exact_sums(part, codes, count)
|
|
234
|
+
out = np.full(count, np.nan, dtype=np.float32)
|
|
235
|
+
undefined = (nan & (not na_rm)) | (plus & minus)
|
|
236
|
+
infinite = (plus | minus) & ~undefined
|
|
237
|
+
out[infinite] = np.where(plus[infinite], np.float32(np.inf), np.float32(-np.inf))
|
|
238
|
+
regular = ~(undefined | infinite)
|
|
235
239
|
if op == "mean":
|
|
236
|
-
|
|
240
|
+
regular &= present > 0 # a mean of no values stays NaN
|
|
241
|
+
divisor = present if op == "mean" else np.ones(count, dtype=np.int64)
|
|
242
|
+
whole, fits = _whole(buckets)
|
|
243
|
+
fast = regular & fits & (divisor < 2**35) # so the integer quotient below stays within int64
|
|
244
|
+
if fast.any():
|
|
245
|
+
out[fast] = _rounded(whole[fast], low - 150, divisor[fast])
|
|
246
|
+
for k in np.flatnonzero(regular & ~fits):
|
|
247
|
+
row = buckets[k]
|
|
248
|
+
total = sum(int(row[j]) << int(j) for j in np.flatnonzero(row)) * Fraction(2) ** (low - 150)
|
|
249
|
+
out[k] = _round32(total / int(divisor[k]))
|
|
237
250
|
return out
|
|
238
251
|
|
|
239
252
|
|
|
240
|
-
def _exact_sums(values, codes, count
|
|
241
|
-
"""Exact sums as
|
|
253
|
+
def _exact_sums(values, codes, count):
|
|
254
|
+
"""Exact sums as integer buckets per group and exponent, the lowest exponent, values present, and specials.
|
|
242
255
|
|
|
243
256
|
A float32 is M * 2**(e - 150) with M a signed integer below 2**24 and e
|
|
244
257
|
from 1 to 254. Integers M with the same e add exactly in float64 for
|
|
245
258
|
fewer than 2**29 values, so they are summed per group and exponent with
|
|
246
|
-
``np.bincount
|
|
259
|
+
``np.bincount``: group k's exact sum is the sum over j of
|
|
260
|
+
``buckets[k, j] * 2**(low + j - 150)``. Also, per group, the values
|
|
261
|
+
present (not NaN), and whether a NaN, +inf, or -inf is among them.
|
|
247
262
|
"""
|
|
248
263
|
bits = values.view(np.uint32)
|
|
249
264
|
exponent = ((bits >> 23) & 0xFF).astype(np.intp)
|
|
@@ -257,30 +272,70 @@ def _exact_sums(values, codes, count, na_rm):
|
|
|
257
272
|
has_nan = np.bincount(codes, weights=nan, minlength=count) > 0
|
|
258
273
|
plus = np.bincount(codes, weights=values == np.inf, minlength=count) > 0
|
|
259
274
|
minus = np.bincount(codes, weights=values == -np.inf, minlength=count) > 0
|
|
260
|
-
|
|
275
|
+
low, width = 1, 1
|
|
261
276
|
if finite.any():
|
|
262
277
|
low, high = int(exponent[finite].min()), int(exponent[finite].max())
|
|
263
278
|
width = high - low + 1
|
|
279
|
+
buckets = np.zeros(count * width, dtype=np.int64)
|
|
280
|
+
if finite.any():
|
|
264
281
|
key = codes[finite] * width + (exponent[finite] - low)
|
|
265
282
|
weights = mantissa[finite].astype(np.float64)
|
|
266
|
-
buckets = np.zeros(count * width, dtype=np.int64)
|
|
267
283
|
chunk = 1 << 28 # each bucket sum stays below 2**52, so float64 holds it exactly
|
|
268
284
|
for start in range(0, len(key), chunk):
|
|
269
285
|
buckets += np.bincount(key[start:start + chunk], weights=weights[start:start + chunk],
|
|
270
286
|
minlength=count * width).astype(np.int64)
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
287
|
+
return buckets.reshape(count, width), low, present, has_nan, plus, minus
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _whole(buckets: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
|
|
291
|
+
"""Each group's exact sum as one integer (the buckets shifted into place), and where it is below 2**52.
|
|
292
|
+
|
|
293
|
+
Where it is, the shifted buckets and their sum stay within int64 and the
|
|
294
|
+
sum is exact in float64 too; other groups need Python integers.
|
|
295
|
+
"""
|
|
296
|
+
shifts = np.arange(buckets.shape[1])
|
|
297
|
+
bound = (np.abs(buckets).astype(np.float64) * np.ldexp(1.0, shifts)).sum(axis=1) # exact terms; rounding is small
|
|
298
|
+
fits = bound < 2.0**52
|
|
299
|
+
whole = np.zeros(len(buckets), dtype=np.int64)
|
|
300
|
+
if fits.any():
|
|
301
|
+
# Shifts of zero buckets beyond 62 bits are clamped; a nonzero bucket that far up does not fit.
|
|
302
|
+
whole[fits] = np.left_shift(buckets[fits], np.minimum(shifts, 62)).sum(axis=1)
|
|
303
|
+
return whole, fits
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _rounded(whole: np.ndarray, scale: int, divisor: np.ndarray) -> np.ndarray:
|
|
307
|
+
"""``whole * 2**scale / divisor`` correctly rounded to float32, ties to even, for |whole| below 2**52.
|
|
308
|
+
|
|
309
|
+
The quotient is taken in integers to 27 or 28 significant bits, with its
|
|
310
|
+
last bit set when anything was left over ("round to odd"); with at least
|
|
311
|
+
two bits more than float32 keeps, the one rounding from float64 to
|
|
312
|
+
float32 then rounds as the exact quotient would, subnormals included.
|
|
313
|
+
"""
|
|
314
|
+
sign = np.where(whole < 0, -1.0, 1.0)
|
|
315
|
+
size = np.abs(whole)
|
|
316
|
+
divisor = divisor.astype(np.int64)
|
|
317
|
+
zero = size == 0
|
|
318
|
+
size = np.where(zero, 1, size) # placeholders; zero sums are +0.0, as NumPy gives them
|
|
319
|
+
size_bits = np.frexp(size.astype(np.float64))[1].astype(np.int64) # bit lengths, exact below 2**53
|
|
320
|
+
divisor_bits = np.frexp(divisor.astype(np.float64))[1].astype(np.int64)
|
|
321
|
+
up = 27 + divisor_bits - size_bits # shift so the quotient has 27 or 28 bits
|
|
322
|
+
quotient = np.empty(len(size), dtype=np.int64)
|
|
323
|
+
rest = np.empty(len(size), dtype=bool)
|
|
324
|
+
left = up >= 0
|
|
325
|
+
if left.any(): # size << up has 27 or 28 more bits than the divisor: below 2**63 for divisors below 2**35
|
|
326
|
+
numerator = np.left_shift(size[left], up[left])
|
|
327
|
+
quotient[left], remainder = np.divmod(numerator, divisor[left])
|
|
328
|
+
rest[left] = remainder != 0
|
|
329
|
+
right = ~left
|
|
330
|
+
if right.any():
|
|
331
|
+
whole_part, remainder = np.divmod(size[right], divisor[right])
|
|
332
|
+
down = -up[right]
|
|
333
|
+
quotient[right] = np.right_shift(whole_part, down)
|
|
334
|
+
rest[right] = (remainder != 0) | ((whole_part & ((np.int64(1) << down) - 1)) != 0)
|
|
335
|
+
quotient |= rest.astype(np.int64)
|
|
336
|
+
result = np.ldexp(sign * quotient.astype(np.float64), scale - up).astype(np.float32)
|
|
337
|
+
result[zero] = np.float32(0.0)
|
|
338
|
+
return result
|
|
284
339
|
|
|
285
340
|
|
|
286
341
|
def _round32(value: Fraction) -> np.float32:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: labelarray
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Labelled N-D arrays in pure NumPy, with exact label semantics and reproducible reductions
|
|
5
5
|
Author: Rigoberto Leyva Salmeron
|
|
6
6
|
License-Expression: MIT
|
|
@@ -46,7 +46,7 @@ It is deliberately small. What it offers that other array libraries do not:
|
|
|
46
46
|
3. **Collections.** Arrays sharing axes, with one label array per axis,
|
|
47
47
|
joined and aligned under those rules.
|
|
48
48
|
|
|
49
|
-
**Status:** version `0.1.
|
|
49
|
+
**Status:** version `0.1.1`; the API may still
|
|
50
50
|
change before 1.0. What changed in each release is in
|
|
51
51
|
[CHANGELOG.md](CHANGELOG.md).
|
|
52
52
|
|
|
@@ -61,9 +61,14 @@ def expected(values: np.ndarray, op: str, na_rm: bool) -> np.float32:
|
|
|
61
61
|
|
|
62
62
|
|
|
63
63
|
def same_bits(a, b) -> bool:
|
|
64
|
-
|
|
64
|
+
return not differing(a, b).size
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def differing(a, b) -> np.ndarray:
|
|
68
|
+
"""Where *a* and *b* differ in their bits, NaN counting as equal to NaN."""
|
|
69
|
+
a, b = np.atleast_1d(np.asarray(a, F32)), np.atleast_1d(np.asarray(b, F32))
|
|
65
70
|
both_nan = np.isnan(a) & np.isnan(b)
|
|
66
|
-
return
|
|
71
|
+
return np.flatnonzero(~(both_nan | (a.view(np.uint32) == b.view(np.uint32))))
|
|
67
72
|
|
|
68
73
|
|
|
69
74
|
def corpus():
|
|
@@ -233,3 +238,84 @@ def test_compiled_kernels_run_only_from_a_size_where_they_pay(monkeypatch):
|
|
|
233
238
|
sums.reduce(many, "sum", (0,))
|
|
234
239
|
sums.reduce(many[0, :sums.COMPILED_FROM], "mean", (0,), codes=rng.integers(0, 5, sums.COMPILED_FROM), groups=5)
|
|
235
240
|
assert calls == ["in_order", "grouped_estimate"]
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
# ── Many tiny groups, where exact means and sums often lie on float32 ties ──
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def tiny_groups(seed, count=3000, spread=(0.0, 2.4)):
|
|
247
|
+
"""Groups of 1 to 4 float32 values, many of whose exact sums and means are float32 ties."""
|
|
248
|
+
rng = np.random.default_rng(seed)
|
|
249
|
+
sizes = rng.integers(1, 5, count)
|
|
250
|
+
keys = np.repeat(np.arange(count), sizes)
|
|
251
|
+
values = rng.uniform(0, 255, len(keys)) * 10.0 ** rng.uniform(*spread, len(keys)) * rng.choice([-1, 1], len(keys))
|
|
252
|
+
values = values.astype(F32)
|
|
253
|
+
order = rng.permutation(len(values))
|
|
254
|
+
return values[order], keys[order]
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _reduced(values, keys, count, op, na_rm=False, min_count=None):
|
|
258
|
+
return sums.reduce(values[:, None], op, (0,), codes=keys, groups=count, na_rm=na_rm, min_count=min_count)[:, 0]
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
@pytest.mark.parametrize("seed", range(6))
|
|
262
|
+
@pytest.mark.parametrize("op", ["sum", "mean"])
|
|
263
|
+
def test_tiny_groups_on_ties_are_correctly_rounded(seed, op):
|
|
264
|
+
values, keys = tiny_groups(seed)
|
|
265
|
+
count = int(keys.max()) + 1
|
|
266
|
+
got = _reduced(values, keys, count, op)
|
|
267
|
+
groups = [values[keys == k] for k in range(count)]
|
|
268
|
+
want = np.array([expected(g, op, False) for g in groups], F32)
|
|
269
|
+
wrong = differing(got, want)
|
|
270
|
+
assert not wrong.size, [(k, groups[k], got[k], want[k]) for k in wrong[:5]]
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
@pytest.mark.parametrize("na_rm", [False, True])
|
|
274
|
+
def test_tiny_groups_with_subnormals_zeros_and_missing_values(na_rm):
|
|
275
|
+
rng = np.random.default_rng(7)
|
|
276
|
+
groups = []
|
|
277
|
+
for _ in range(2000):
|
|
278
|
+
size = int(rng.integers(1, 5))
|
|
279
|
+
kind = rng.integers(0, 6)
|
|
280
|
+
if kind == 0:
|
|
281
|
+
group = rng.integers(-2**23, 2**23, size) * 2.0**-149 # subnormals, and ties between them
|
|
282
|
+
elif kind == 1:
|
|
283
|
+
group = rng.choice([0.0, -0.0], size)
|
|
284
|
+
elif kind == 2:
|
|
285
|
+
group = np.where(rng.random(size) < 0.4, np.nan, rng.uniform(0, 255, size))
|
|
286
|
+
elif kind == 3:
|
|
287
|
+
group = rng.uniform(1, 2, size) * rng.choice([1, 2**-126, 2**100], size) # narrow and wide spreads
|
|
288
|
+
elif kind == 4:
|
|
289
|
+
group = rng.choice([np.inf, -np.inf, 1.0], size)
|
|
290
|
+
else:
|
|
291
|
+
group = rng.uniform(0, 255, size)
|
|
292
|
+
groups.append(np.asarray(group, F32))
|
|
293
|
+
values = np.concatenate(groups)
|
|
294
|
+
keys = np.repeat(np.arange(len(groups)), [len(g) for g in groups])
|
|
295
|
+
for op in ("sum", "mean"):
|
|
296
|
+
got = _reduced(values, keys, len(groups), op, na_rm=na_rm)
|
|
297
|
+
want = np.array([expected(g, op, na_rm) for g in groups], F32)
|
|
298
|
+
wrong = differing(got, want)
|
|
299
|
+
assert not wrong.size, [(op, k, groups[k], got[k], want[k]) for k in wrong[:5]]
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def test_narrow_groups_are_decided_without_fractions(monkeypatch):
|
|
303
|
+
values, keys = tiny_groups(11, count=5000)
|
|
304
|
+
count = int(keys.max()) + 1
|
|
305
|
+
monkeypatch.setattr(sums, "_round32", lambda value: pytest.fail("a narrow group was decided with fractions"))
|
|
306
|
+
for op in ("sum", "mean"):
|
|
307
|
+
_reduced(values, keys, count, op)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def test_groups_spanning_wide_exponents_still_add_exactly(monkeypatch):
|
|
311
|
+
big = 2.0**100
|
|
312
|
+
groups = [[big, 1.0, -big], [1e30, 3.0, -1e30, 1.0], [2.0**-140, 2.0**120, -(2.0**120)]]
|
|
313
|
+
values = np.concatenate([np.asarray(g, F32) for g in groups])
|
|
314
|
+
keys = np.repeat(np.arange(len(groups)), [len(g) for g in groups])
|
|
315
|
+
calls = []
|
|
316
|
+
original = sums._round32
|
|
317
|
+
monkeypatch.setattr(sums, "_round32", lambda value: calls.append(value) or original(value))
|
|
318
|
+
for op in ("sum", "mean"):
|
|
319
|
+
got = _reduced(values, keys, len(groups), op)
|
|
320
|
+
assert same_bits(got, np.array([expected(np.asarray(g, F32), op, False) for g in groups], F32))
|
|
321
|
+
assert calls # these need the exact path in Python integers
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|