Skip to content

Commit

Permalink
Merge pull request #59 from CSchoel/dev
Browse files Browse the repository at this point in the history
Merge 0.6.1 into main
  • Loading branch information
CSchoel authored Oct 1, 2024
2 parents 924753b + 0d274e8 commit b91761c
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 14 deletions.
8 changes: 6 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
on:
push:
branches: ["main", "dev", "v*"]
pull_request:

name: build

Expand All @@ -9,14 +10,17 @@ jobs:
strategy:
matrix:
python: ["3.7", "3.10"]
extras: ["", "[RANSAC, qrandom, plots]"]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
- run: pip install .
- run: pip install ".${{ matrix.extras }}"
- run: pip install codecov .
- run: coverage run -m unittest nolds.test_measures
- run: codecov
if: ${{ matrix.python == '3.10' }}
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
if: ${{ matrix.python == '3.10' && matrix.extras != '' }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ _build

# virtual environment
.venv
.venv*
15 changes: 14 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Changed
### Fixed

## [0.6.1]

### Added

* Regression tests for all major algorithms that check for small changes in the main output value.

### Changed

* Nolds now supports numpy 2.x as well as 1.x.

### Fixed

## [0.6.0]

### Added
Expand Down Expand Up @@ -221,7 +233,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Hurst exponent (`hurst_rs`)
- detrended fluctuation analysis (DFA) (`dfa`)

[Unreleased]: https://github.com/CSchoel/nolds/compare/0.6.0..HEAD
[Unreleased]: https://github.com/CSchoel/nolds/compare/0.6.1..HEAD
[0.6.1]: https://github.com/CSchoel/nolds/compare/0.6.0..0.6.1
[0.6.0]: https://github.com/CSchoel/nolds/compare/0.5.2..0.6.0
[0.5.2]: https://github.com/CSchoel/nolds/compare/0.5.1..0.5.2
[0.5.1]: https://github.com/CSchoel/nolds/compare/0.5.0..0.5.1
Expand Down
2 changes: 1 addition & 1 deletion doc/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
# The short X.Y version.
version = '0.6'
# The full version, including alpha/beta/rc tags.
release = '0.6.0'
release = '0.6.1'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
Expand Down
12 changes: 8 additions & 4 deletions nolds/datasets.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,19 @@ def lorenz_euler(length, sigma, rho, beta, dt=0.01, start=[1,1,1]):
"""
def lorenz(state, sigma, rho, beta):
x, y, z = state
# NOTE: Numpy 1.x stores intermediate results as float64
# => to achieve consistency between numpy versions, we have to use
# float32 for all values that enter the formula to simulate numpy 1.x
# behavior with numpy 2.x.
return np.array([
sigma * (y - x),
rho * x - y - x * z,
x * y - beta * z
np.float32(sigma) * (y - x),
np.float32(rho) * x - y - x * z,
x * y - np.float32(beta) * z
], dtype="float32")
trajectory = np.zeros((length, 3), dtype="float32")
trajectory[0] = start
for i in range(1, length):
t = i * dt
# t = i * dt
trajectory[i] = trajectory[i-1] + lorenz(trajectory[i-1], sigma, rho, beta) * dt
return trajectory

Expand Down
2 changes: 1 addition & 1 deletion nolds/measures.py
Original file line number Diff line number Diff line change
Expand Up @@ -1442,7 +1442,7 @@ def mfhurst_b(data, qvals=None, dists=None, fit='poly',
Essentially, we can calculate c_q(d) of a discrete evenly sampled time
series Y = [y_0, y_1, y_2, ... y_(N-1)] by taking the absolute differences
[|y_0 - y_d|, |y_1 - y_(d+1)|, ... , |y_(N-d-1) - y_(N-1)|] raising them to
[\|y_0 - y_d\|, \|y_1 - y_(d+1)\|, ... , \|y_(N-d-1) - y_(N-1)\|] raising them to
the qth power and taking the mean.
Now we take the logarithm on both sides of our relation c_q(d) ~ d^(q H_q)
Expand Down
131 changes: 129 additions & 2 deletions nolds/test_measures.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def assert_array_equals(self, expected, actual, print_arrays=False):
print("==")
print(expected)
print()
self.assertTrue(np.alltrue(actual == expected))
self.assertTrue(np.all(actual == expected))

def test_delay_embed_lag2(self):
data = np.arange(10, dtype="float32")
Expand Down Expand Up @@ -459,7 +459,7 @@ def test_lorenz(self):
x = data[discard:,1]
rvals = nolds.logarithmic_r(1, np.e, 1.1) # determined experimentally
cd = nolds.corr_dim(x, emb_dim, fit="poly", rvals=rvals, lag=lag)
self.assertAlmostEqual(cd, 2.05, delta=0.1)
self.assertAlmostEqual(cd, 2.05, delta=0.2)

def test_logistic(self):
# TODO replicate tests with logistic map from grassberger-procaccia
Expand Down Expand Up @@ -544,5 +544,132 @@ def test_sampen_lorenz(self):
self.assertAlmostEqual(0.25, sz, delta=0.05)


class RegressionTests(unittest.TestCase):
"""Regression tests for main algorithms.
These tests are here to safeguard against accidental algorithmic changes such
as updates to core dependencies such as numpy or the Python standard library.
"""

def test_sampen(self):
"""Test hypothesis: The exact output of sampen() on random data hasn't changed since the last version."""
data = datasets.load_qrandom()[:1000]
se = nolds.sampen(data, emb_dim=2, tolerance=None, lag=1, dist=nolds.rowwise_chebyshev, closed=False)
self.assertAlmostEqual(2.1876999522832743, se, places=15)

def test_corr_dim(self):
"""Test hypothesis: The exact output of corr_dim() with `fit=poly` on random data hasn't changed since the last version."""
data = datasets.load_qrandom()[:1000]
cd = nolds.corr_dim(data, emb_dim=5, lag=1, rvals=None, dist=nolds.rowwise_euclidean, fit="poly")
self.assertAlmostEqual(1.303252839255068, cd, places=15)

@unittest.skipUnless(SCIPY_AVAILABLE, "Tests with RANSAC require scipy.")
def test_corr_dim_RANSAC(self):
"""Test hypothesis: The exact output of corr_dim() with `fit=RANSAC` on random data hasn't changed since the last version."""
data = datasets.load_qrandom()[:1000]
sd = np.std(data, ddof=1)
# fix seed
np.random.seed(42)
# usa a too wide range for rvals to give RANSAC something to do ;)
rvals = nolds.logarithmic_r(0.01 * sd, 2 * sd, 1.03)
cd = nolds.corr_dim(data, emb_dim=5, lag=1, rvals=rvals, dist=nolds.rowwise_euclidean, fit="RANSAC")
self.assertAlmostEqual(0.44745494643404665, cd, places=15)

def test_lyap_e(self):
"""Test hypothesis: The exact output of lyap_e() on random data hasn't changed since the last version."""
data = datasets.load_qrandom()[:1000]
le = nolds.lyap_e(data, emb_dim=10, matrix_dim=4, min_nb=10, min_tsep=1, tau=1)
expected = np.array([ 0.03779942603329712, -0.014314012551504982, -0.08436867977030214, -0.22316730257003717])
for i in range(le.shape[0]):
self.assertAlmostEqual(expected[i], le[i], places=15, msg=f"{i+1}th Lyapunov exponent doesn't match")

def test_lyap_r(self):
"""Test hypothesis: The exact output of lyap_r() with `fit=poly` on random data hasn't changed since the last version."""
data = datasets.load_qrandom()[:1000]
le = nolds.lyap_r(data, emb_dim=10, lag=1, min_tsep=1, tau=1, min_neighbors=10, trajectory_len=10, fit="poly")
expected = 0.094715945307378
self.assertAlmostEqual(expected, le, places=15)

@unittest.skipUnless(SCIPY_AVAILABLE, "Tests with RANSAC require scipy.")
def test_lyap_r_RANSAC(self):
"""Test hypothesis: The exact output of lyap_r() with `fit=RANSAC` on random data hasn't changed since the last version."""
data = datasets.load_qrandom()[:1000]
np.random.seed(42)
# set lag to 2 for weird duplicate lines
# set trajectory_len to 100 to get many datapoints for RANSAC to choose from
le = nolds.lyap_r(data, emb_dim=10, lag=2, min_tsep=1, tau=1, min_neighbors=10, trajectory_len=100, fit="RANSAC")
expected = 0.0003401212353253564
self.assertAlmostEqual(expected, le, places=15)

def test_hurst_rs(self):
"""Test hypothesis: The exact output of hurst_rs() with `fit=poly` on random data hasn't changed since the last version."""
data = datasets.load_qrandom()[:1000]
rs = nolds.hurst_rs(data, nvals=None, fit="poly", corrected=True, unbiased=True)
expected = 0.5123887964986258
self.assertAlmostEqual(expected, rs, places=15)

@unittest.skipUnless(SCIPY_AVAILABLE, "Tests with RANSAC require scipy.")
def test_hurst_rs_RANSAC(self):
"""Test hypothesis: The exact output of hurst_rs() with `fit=RANSAC` on random data hasn't changed since the last version."""
data = datasets.load_qrandom()[:1000]
np.random.seed(42)
# increase nsteps in nvals to have more data points for RANSAC to choose from
nvals = nolds.logmid_n(data.shape[0], ratio=1/4.0, nsteps=100)
rs = nolds.hurst_rs(data, nvals=nvals, fit="RANSAC", corrected=True, unbiased=True)
expected = 0.4805431939943321
self.assertAlmostEqual(expected, rs, places=15)

def test_dfa(self):
"""Test hypothesis: The exact output of dfa() with `fit_exp=poly` on random data hasn't changed since the last version."""
data = datasets.load_qrandom()[:1000]
h = nolds.dfa(data, nvals=None, overlap=True, order=1, fit_trend="poly", fit_exp="poly")
expected = 0.5450874638765073
self.assertAlmostEqual(expected, h, places=15)

@unittest.skipUnless(SCIPY_AVAILABLE, "Tests with RANSAC require scipy.")
def test_dfa_RANSAC(self):
"""Test hypothesis: The exact output of dfa() with `fit_exp=RANSAC` on random data hasn't changed since the last version."""
# adds trend to data to introduce a less clear line for fitting
data = datasets.load_qrandom()[:1000] + np.arange(1000) * 100
np.random.seed(42)
# adds more steps and higher values to nvals to introduce some scattering for RANSAC to have an effect on
nvals = nolds.logarithmic_n(10, 0.9 * data.shape[0], 1.1)
h = nolds.dfa(data, nvals=nvals, overlap=True, order=1, fit_trend="poly", fit_exp="RANSAC")
expected = 1.1372303125405405
self.assertAlmostEqual(expected, h, places=15)

def test_mfhurst_b(self):
"""Test hypothesis: The exact output of mfhurst_b() with `fit=poly` on random data hasn't changed since the last version."""
data = datasets.load_qrandom()[:1000]
h = nolds.mfhurst_b(data, qvals=[1], dists=None, fit="poly")
expected = [-0.00559398934417339]
self.assertAlmostEqual(expected[0], h[0], places=15)

@unittest.skipUnless(SCIPY_AVAILABLE, "Tests with RANSAC require scipy.")
def test_mfhurst_b_RANSAC(self):
"""Test hypothesis: The exact output of mfhurst_b() with `fit=RANSAC` on random data hasn't changed since the last version."""
data = datasets.load_qrandom()[:1000]
np.random.seed(42)
h = nolds.mfhurst_b(data, qvals=[1], dists=None, fit="RANSAC")
expected = [-0.009056463064211057]
self.assertAlmostEqual(expected[0], h[0], places=15)

def test_mfhurst_dm(self):
"""Test hypothesis: The exact output of mfhurst_dm() with `fit=poly` on random data hasn't changed since the last version."""
data = datasets.load_qrandom()[:1000]
h, _ = nolds.mfhurst_dm(data, qvals=[1], max_dists=range(5, 20), detrend=True, fit="poly")
expected = [0.008762803881203145]
self.assertAlmostEqual(expected[0], h[0], places=15)

@unittest.skipUnless(SCIPY_AVAILABLE, "Tests with RANSAC require scipy.")
def test_mfhurst_dm_RANSAC(self):
"""Test hypothesis: The exact output of mfhurst_dm() with `fit=RANSAC` on random data hasn't changed since the last version."""
data = datasets.load_qrandom()[:1000]
np.random.seed(42)
h, _ = nolds.mfhurst_dm(data, qvals=[1], max_dists=range(5, 20), detrend=True, fit="RANSAC")
expected = [0.005324834328837356]
self.assertAlmostEqual(expected[0], h[0], places=15)


if __name__ == "__main__":
unittest.main()
6 changes: 3 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def run(self):

with io.open("README.rst", "r", encoding="utf-8") as f:
readme = f.read()
version = '0.6.0'
version = '0.6.1'
setup(
name='nolds',
packages=['nolds'],
Expand Down Expand Up @@ -57,12 +57,12 @@ def run(self):
],
test_suite='nolds.test_measures',
install_requires=[
'numpy<2.0',
'numpy>1.0,<3.0',
'future',
'setuptools'
],
extras_require={
'RANSAC': ['sklearn>=0.19'],
'RANSAC': ['scikit-learn>=0.19'],
'qrandom': ['quantumrandom'],
'plots': ['matplotlib']
},
Expand Down

0 comments on commit b91761c

Please sign in to comment.