From f4f94e69b8857ca8d51ddd56ce7c3a600cf2fc7c Mon Sep 17 00:00:00 2001 From: Anton Soloviev Date: Sat, 10 Oct 2026 20:51:09 -0700 Subject: [PATCH 1/3] Preserve exact comparisons in fast permutation entropy --- src/antropy/entropy.py | 36 ++++++++++++++--------------------- tests/test_entropy.py | 43 +++++++++++++++++++++++++++++++++++------- 2 files changed, 50 insertions(+), 29 deletions(-) diff --git a/src/antropy/entropy.py b/src/antropy/entropy.py index 34cb8e8..8c3f2ae 100644 --- a/src/antropy/entropy.py +++ b/src/antropy/entropy.py @@ -31,9 +31,8 @@ # the ordinal index from a small pre-computed table. This avoids the O(m # log m) sort and replaces it with O(m) bitwise operations. # -# Note: equal values (ties) are encoded as False by '<', which may assign -# them to a different ordinal pattern than argsort would. For the typical -# case of continuous data this never occurs in practice. +# Comparisons use '<=' because the left column always has the earlier +# position. This breaks ties by position, matching stable argsort. # --------------------------------------------------------------------------- # order=3 — key bits: bit2=(a Date: Tue, 13 Oct 2026 02:20:15 -0700 Subject: [PATCH 2/3] Isolate missing epochs before ordinal pattern lookup --- src/antropy/entropy.py | 17 +++++++++++++---- tests/test_entropy.py | 25 +++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/antropy/entropy.py b/src/antropy/entropy.py index 8c3f2ae..5fd745e 100644 --- a/src/antropy/entropy.py +++ b/src/antropy/entropy.py @@ -35,7 +35,7 @@ # position. This breaks ties by position, matching stable argsort. # --------------------------------------------------------------------------- -# order=3 — key bits: bit2=(a Date: Tue, 13 Oct 2026 02:23:11 -0700 Subject: [PATCH 3/3] Preserve object comparisons when identifying missing epochs --- src/antropy/entropy.py | 3 ++- tests/test_entropy.py | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/antropy/entropy.py b/src/antropy/entropy.py index 5fd745e..797b0d6 100644 --- a/src/antropy/entropy.py +++ b/src/antropy/entropy.py @@ -88,7 +88,8 @@ def _perm_entropy_fast(x, order, delay, normalize): # NaN comparisons do not define an ordering and can produce invalid keys. # Process valid epochs separately so a missing sample cannot affect them. - valid_epochs = ~np.isnan(x).any(axis=1) + # Self-comparison detects NaN without requiring a float dtype. + valid_epochs = (x == x).all(axis=1) if not valid_epochs.all(): result = np.full(n, np.nan) result[valid_epochs] = _perm_entropy_fast(x[valid_epochs], order, delay, normalize) diff --git a/tests/test_entropy.py b/tests/test_entropy.py index 27824ce..01b9f89 100644 --- a/tests/test_entropy.py +++ b/tests/test_entropy.py @@ -149,6 +149,7 @@ def test_perm_entropy_preserves_ordering(self): x, x + 2**60, x.astype(np.uint64) + 2**63, + np.array([int(value) + 2**100 for value in x], dtype=object), x * 1e-18, 1.0 + x * np.finfo(float).eps, ] @@ -181,6 +182,9 @@ def test_perm_entropy_nan_epoch_isolation(self): for order in [3, 4]: for normalize in [False, True]: self.assertTrue(np.isnan(perm_entropy(missing, order, normalize=normalize))) + self.assertTrue( + np.isnan(perm_entropy(missing.astype(object), order, normalize=normalize)) + ) self.assertTrue(np.isnan(perm_entropy(np.vstack([missing, missing]), order)).all()) expected = _perm_entropy_orig(values, order, normalize=normalize) for position in [0, 1, 2]: