Skip to content

Commit ff3cc2c

Browse files
authored
🤖 switch to maturin for build & release (#4)
* 🤖 switch to maturin for build & release * 🕳️ add empty _rust subfolder * 🧹 * 🧹 * 💫 head/tail recursion for build macro * 🙈 fix release nb * 🧹 * ♻️ pass minmax_ratio in Python bindings * 🙈 addminmax_ratio to minmaxlttb * 😬 further disable mypy checks * 🧹 formatting * 🙈 fix benches * :clown: format subpackage
1 parent 0dc8d56 commit ff3cc2c

File tree

21 files changed

+365
-169
lines changed

21 files changed

+365
-169
lines changed

.github/workflows/ci-tsdownsample.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@ jobs:
2121
- uses: actions/setup-python@v4
2222
with:
2323
python-version: '3.10'
24-
- run: pip install -r tests/requirements-linting.txt
25-
2624
- name: Install Rust toolchain
2725
uses: actions-rs/toolchain@v1
2826
with:
@@ -36,6 +34,7 @@ jobs:
3634
- name: Cache rust
3735
uses: Swatinem/rust-cache@v2
3836

37+
- run: pip install -r tests/requirements-linting.txt
3938
- run: pip freeze
4039
- run: make lint # Lint Python & Rust
4140
- run: make mypy # Type check Python

Cargo.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
[package]
2-
name = "tsdownsample_rs"
2+
name = "tsdownsample" # Same name as the Python package
33
version = "0.1.0"
44
edition = "2021"
55
authors = ["Jeroen Van Der Donckt"]
66
description = "Python bindings for time series downsampling algorithms"
77
repository = "https://github.com/predict-idlab/tsdownsample"
88
license = "MIT"
99

10+
[package.metadata.maturin]
11+
# Import the Rust library under this path
12+
# See: https://www.maturin.rs/project_layout.html#import-rust-as-a-submodule-of-your-project
13+
name = "tsdownsample._rust._tsdownsample_rs"
1014

1115
[dependencies]
1216
downsample_rs = { path = "downsample_rs", features = ["half"]}
@@ -16,5 +20,5 @@ half = { version = "2.1", default-features = false }
1620
paste = { version = "1.0.9", default-features = false }
1721

1822
[lib]
19-
name = "tsdownsample_rs"
23+
name = "tsdownsample"
2024
crate-type = ["cdylib"]

Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ isort = isort tsdownsample tests
33
black = black tsdownsample tests
44

55
install:
6-
pip install -r requirements.txt
76
pip install -e .
87

98
.PHONY: format
@@ -47,6 +46,7 @@ clean:
4746
rm -f `find . -type f -name '*.py[co]' `
4847
rm -f `find . -type f -name '*~' `
4948
rm -f `find . -type f -name '.*~' `
49+
rm -f `find . -type f -name '*.cpython-*' `
5050
rm -rf dist
5151
rm -rf build
5252
rm -rf target
@@ -55,8 +55,8 @@ clean:
5555
rm -rf .mypy_cache
5656
rm -rf htmlcov
5757
rm -rf *.egg-info
58+
rm -rf .ruff*
5859
rm -f .coverage
5960
rm -f .coverage.*
6061
rm -rf build
61-
rm -f tsdownsample/*.so
62-
python setup.py clean
62+
rm -f tsdownsample/*.so

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
<summary><i>!! 🚀 <code>f16</code> <a href="https://github.com/jvdd/argminmax">argminmax</a> is 200-300x faster than numpy</i></summary>
3131
In contrast with all other data types above, <code>f16</code> is *not* hardware supported (i.e., no instructions for f16) by most modern CPUs!! <br>
3232
🐌 Programming languages facilitate support for this datatype by either (i) upcasting to `f32` or (ii) using a software implementation. <br>
33-
💡 As for argminmax, only comparisons are needed - and thus no arithmetic operations - creating a <ins>symmetrical ordinal mapping from <code>f16</code> to <code>i16</code></ins> is sufficient. This mapping allows to use the hardware supported scalar and SIMD <code>i16</code> instructions - while not producing any memory overhead 🎉 <br>
33+
💡 As for argminmax, only comparisons are needed - and thus no arithmetic operations - creating a <u>symmetrical ordinal mapping from <code>f16</code> to <code>i16</code></u> is sufficient. This mapping allows to use the hardware supported scalar and SIMD <code>i16</code> instructions - while not producing any memory overhead 🎉 <br>
3434
<i>More details are described in <a href="https://github.com/jvdd/argminmax/pull/1">argminmax PR #1</a>.</i>
3535
</details>
3636
* **Easy to use**: simple & flexible API
@@ -47,7 +47,7 @@ pip install tsdownsample
4747
## Usage
4848

4949
```python
50-
from tsdownsample import MinMaxLTTBDowsampler
50+
from tsdownsample import MinMaxLTTBDownsampler
5151
import pandas as pd; import numpy as np
5252

5353
# Create a time series

downsample_rs/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ license = "MIT"
88

99
[dependencies]
1010
ndarray = {version = "0.15.6", default-features = false, features = ["rayon"] }
11-
argminmax = { version = "0.2" , features = ["half"] }
11+
argminmax = { version = "0.2.1" , features = ["half"] }
1212
half = { version = "2.1", default-features = false , optional = true}
1313

1414
[dev-dependencies]

downsample_rs/benches/bench_minmaxlttb.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ use criterion::{black_box, Criterion};
88
use dev_utils::{config, utils};
99
use ndarray::Array1;
1010

11+
const MINMAX_RATIO: usize = 30;
12+
1113
fn minmaxlttb_f32_random_array_long_single_core(c: &mut Criterion) {
1214
let n = config::ARRAY_LENGTH_LONG;
1315
let x = Array1::from((0..n).map(|i| i as i32).collect::<Vec<i32>>());
@@ -18,6 +20,7 @@ fn minmaxlttb_f32_random_array_long_single_core(c: &mut Criterion) {
1820
black_box(x.view()),
1921
black_box(y.view()),
2022
black_box(2_000),
23+
black_box(MINMAX_RATIO),
2124
)
2225
})
2326
});
@@ -27,6 +30,7 @@ fn minmaxlttb_f32_random_array_long_single_core(c: &mut Criterion) {
2730
black_box(x.view()),
2831
black_box(y.view()),
2932
black_box(2_000),
33+
black_box(MINMAX_RATIO),
3034
)
3135
})
3236
});
@@ -42,6 +46,7 @@ fn minmaxlttb_f32_random_array_long_multi_core(c: &mut Criterion) {
4246
black_box(x.view()),
4347
black_box(y.view()),
4448
black_box(2_000),
49+
black_box(MINMAX_RATIO),
4550
)
4651
})
4752
});
@@ -51,6 +56,7 @@ fn minmaxlttb_f32_random_array_long_multi_core(c: &mut Criterion) {
5156
black_box(x.view()),
5257
black_box(y.view()),
5358
black_box(2_000),
59+
black_box(MINMAX_RATIO),
5460
)
5561
})
5662
});
@@ -66,6 +72,7 @@ fn minmaxlttb_f32_random_array_50M_single_core(c: &mut Criterion) {
6672
black_box(x.view()),
6773
black_box(y.view()),
6874
black_box(2_000),
75+
black_box(MINMAX_RATIO),
6976
)
7077
})
7178
});
@@ -75,6 +82,7 @@ fn minmaxlttb_f32_random_array_50M_single_core(c: &mut Criterion) {
7582
black_box(x.view()),
7683
black_box(y.view()),
7784
black_box(2_000),
85+
black_box(MINMAX_RATIO),
7886
)
7987
})
8088
});
@@ -90,6 +98,7 @@ fn minmaxlttb_f32_random_array_50M_multi_core(c: &mut Criterion) {
9098
black_box(x.view()),
9199
black_box(y.view()),
92100
black_box(2_000),
101+
black_box(MINMAX_RATIO),
93102
)
94103
})
95104
});
@@ -99,6 +108,7 @@ fn minmaxlttb_f32_random_array_50M_multi_core(c: &mut Criterion) {
99108
black_box(x.view()),
100109
black_box(y.view()),
101110
black_box(2_000),
111+
black_box(MINMAX_RATIO),
102112
)
103113
})
104114
});
@@ -109,11 +119,21 @@ fn minmaxlttb_without_x_f32_random_array_50M_single_core(c: &mut Criterion) {
109119
let y = utils::get_random_array::<f32>(n, f32::MIN, f32::MAX);
110120
c.bench_function("mmlttbnox_scal_50M_f32", |b| {
111121
b.iter(|| {
112-
minmaxlttb_mod::minmaxlttb_scalar_without_x(black_box(y.view()), black_box(2_000))
122+
minmaxlttb_mod::minmaxlttb_scalar_without_x(
123+
black_box(y.view()),
124+
black_box(2_000),
125+
black_box(MINMAX_RATIO),
126+
)
113127
})
114128
});
115129
c.bench_function("mmlttbnox_simd_50M_f32", |b| {
116-
b.iter(|| minmaxlttb_mod::minmaxlttb_simd_without_x(black_box(y.view()), black_box(2_000)))
130+
b.iter(|| {
131+
minmaxlttb_mod::minmaxlttb_simd_without_x(
132+
black_box(y.view()),
133+
black_box(2_000),
134+
black_box(MINMAX_RATIO),
135+
)
136+
})
117137
});
118138
}
119139

@@ -125,6 +145,7 @@ fn minmaxlttb_without_x_f32_random_array_50M_multi_core(c: &mut Criterion) {
125145
minmaxlttb_mod::minmaxlttb_scalar_without_x_parallel(
126146
black_box(y.view()),
127147
black_box(2_000),
148+
black_box(MINMAX_RATIO),
128149
)
129150
})
130151
});
@@ -133,6 +154,7 @@ fn minmaxlttb_without_x_f32_random_array_50M_multi_core(c: &mut Criterion) {
133154
minmaxlttb_mod::minmaxlttb_simd_without_x_parallel(
134155
black_box(y.view()),
135156
black_box(2_000),
157+
black_box(MINMAX_RATIO),
136158
)
137159
})
138160
});

downsample_rs/src/m4/generic.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ pub(crate) fn m4_generic<T: Copy + PartialOrd>(
4747
sampled_indices
4848
}
4949

50+
#[inline(always)]
5051
pub(crate) fn m4_generic_parallel<T: Copy + PartialOrd + Send + Sync>(
5152
arr: ArrayView1<T>,
5253
n_out: usize,

downsample_rs/src/minmaxlttb/generic.rs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,46 +3,46 @@ use ndarray::{Array1, ArrayView1};
33
use super::super::lttb::utils::Num;
44
use super::super::lttb::{lttb, lttb_without_x};
55

6-
const SIZE_THRESHOLD: usize = 10_000_000;
7-
const RATIO_THRESHOLD: usize = 100;
8-
const MINMAX_RATIO: usize = 30;
9-
10-
#[inline]
6+
#[inline(always)]
117
pub(crate) fn minmaxlttb_generic<
128
Tx: Num,
139
Ty: Num + PartialOrd, // TODO: check if partialord is needed
1410
>(
1511
x: ArrayView1<Tx>,
1612
y: ArrayView1<Ty>,
1713
n_out: usize,
14+
minmax_ratio: usize,
1815
f_minmax: fn(ArrayView1<Ty>, usize) -> Array1<usize>,
1916
) -> Array1<usize> {
2017
assert_eq!(x.len(), y.len());
21-
// Apply first min max aggregation (if above threshold & ratio)
22-
if x.len() > SIZE_THRESHOLD && x.len() / n_out > RATIO_THRESHOLD {
23-
let index = f_minmax(y, n_out * MINMAX_RATIO);
18+
assert!(minmax_ratio > 1);
19+
// Apply first min max aggregation (if above ratio)
20+
if x.len() / n_out > minmax_ratio {
21+
let index = f_minmax(y, n_out * minmax_ratio);
2422
let x = index.mapv(|i| x[i]);
2523
let y = index.mapv(|i| y[i]);
2624
let index_points_selected = lttb(x.view(), y.view(), n_out);
2725
return index_points_selected.mapv(|i| index[i]);
2826
}
29-
// Apply lttb on all data when requirements are not met
27+
// Apply lttb on all data when requirement is not met
3028
lttb(x, y, n_out)
3129
}
3230

33-
#[inline]
31+
#[inline(always)]
3432
pub(crate) fn minmaxlttb_generic_without_x<Ty: Num>(
3533
y: ArrayView1<Ty>,
3634
n_out: usize,
35+
minmax_ratio: usize,
3736
f_minmax: fn(ArrayView1<Ty>, usize) -> Array1<usize>,
3837
) -> Array1<usize> {
39-
// Apply first min max aggregation (if above threshold & ratio)
40-
if y.len() > SIZE_THRESHOLD && y.len() / n_out > RATIO_THRESHOLD {
41-
let index = f_minmax(y, n_out * 30);
38+
assert!(minmax_ratio > 1);
39+
// Apply first min max aggregation (if above ratio)
40+
if y.len() / n_out > minmax_ratio {
41+
let index = f_minmax(y, n_out * minmax_ratio);
4242
let y = index.mapv(|i| y[i]);
4343
let index_points_selected = lttb(index.view(), y.view(), n_out);
4444
return index_points_selected.mapv(|i| index[i]);
4545
}
46-
// Apply lttb on all data when requirements are not met
46+
// Apply lttb on all data when requirement is not met
4747
lttb_without_x(y.view(), n_out)
4848
}

downsample_rs/src/minmaxlttb/scalar.rs

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,23 @@ pub fn minmaxlttb_scalar<Tx: Num, Ty: Num + PartialOrd>(
1212
x: ArrayView1<Tx>,
1313
y: ArrayView1<Ty>,
1414
n_out: usize,
15+
minmax_ratio: usize,
1516
) -> Array1<usize>
1617
where
1718
SCALAR: ScalarArgMinMax<Ty>,
1819
{
19-
minmaxlttb_generic(x, y, n_out, minmax::min_max_scalar)
20+
minmaxlttb_generic(x, y, n_out, minmax_ratio, minmax::min_max_scalar)
2021
}
2122

2223
pub fn minmaxlttb_scalar_without_x<Ty: Num + PartialOrd>(
2324
y: ArrayView1<Ty>,
2425
n_out: usize,
26+
minmax_ratio: usize,
2527
) -> Array1<usize>
2628
where
2729
SCALAR: ScalarArgMinMax<Ty>,
2830
{
29-
minmaxlttb_generic_without_x(y, n_out, minmax::min_max_scalar)
31+
minmaxlttb_generic_without_x(y, n_out, minmax_ratio, minmax::min_max_scalar)
3032
}
3133

3234
// ------------------------------------- PARALLEL --------------------------------------
@@ -35,33 +37,78 @@ pub fn minmaxlttb_scalar_parallel<Tx: Num + Send + Sync, Ty: Num + PartialOrd +
3537
x: ArrayView1<Tx>,
3638
y: ArrayView1<Ty>,
3739
n_out: usize,
40+
minmax_ratio: usize,
3841
) -> Array1<usize>
3942
where
4043
SCALAR: ScalarArgMinMax<Ty>,
4144
{
42-
minmaxlttb_generic(x, y, n_out, minmax::min_max_scalar_parallel)
45+
minmaxlttb_generic(x, y, n_out, minmax_ratio, minmax::min_max_scalar_parallel)
4346
}
4447

4548
pub fn minmaxlttb_scalar_without_x_parallel<Ty: Num + PartialOrd + Send + Sync>(
4649
y: ArrayView1<Ty>,
4750
n_out: usize,
51+
minmax_ratio: usize,
4852
) -> Array1<usize>
4953
where
5054
SCALAR: ScalarArgMinMax<Ty>,
5155
{
52-
minmaxlttb_generic_without_x(y, n_out, minmax::min_max_scalar_parallel)
56+
minmaxlttb_generic_without_x(y, n_out, minmax_ratio, minmax::min_max_scalar_parallel)
5357
}
5458

5559
// ---- TEST
5660

5761
#[cfg(test)]
5862
mod tests {
59-
extern crate dev_utils;
63+
use super::{minmaxlttb_scalar, minmaxlttb_scalar_without_x};
64+
use super::{minmaxlttb_scalar_parallel, minmaxlttb_scalar_without_x_parallel};
65+
use ndarray::{array, s, Array1};
6066

67+
extern crate dev_utils;
6168
use dev_utils::utils;
6269

63-
use super::{minmaxlttb_scalar, minmaxlttb_scalar_without_x};
64-
use ndarray::{array, s, Array1};
70+
fn get_array_f32(n: usize) -> Array1<f32> {
71+
utils::get_random_array(n, f32::MIN, f32::MAX)
72+
}
73+
74+
#[test]
75+
fn test_minmaxlttb() {
76+
let x = array![0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
77+
let y = array![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0];
78+
let sampled_indices = minmaxlttb_scalar(x.view(), y.view(), 4, 2);
79+
assert_eq!(sampled_indices, array![0, 1, 5, 9]);
80+
}
81+
82+
#[test]
83+
fn test_minmaxlttb_without_x() {
84+
let y = array![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0];
85+
let sampled_indices = minmaxlttb_scalar_without_x(y.view(), 4, 2);
86+
assert_eq!(sampled_indices, array![0, 1, 5, 9]);
87+
}
88+
89+
#[test]
90+
fn test_minmaxlttb_parallel() {
91+
let x = array![0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
92+
let y = array![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0];
93+
let sampled_indices = minmaxlttb_scalar_parallel(x.view(), y.view(), 4, 2);
94+
assert_eq!(sampled_indices, array![0, 1, 5, 9]);
95+
}
96+
97+
#[test]
98+
fn test_minmaxlttb_without_x_parallel() {
99+
let y = array![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0];
100+
let sampled_indices = minmaxlttb_scalar_without_x_parallel(y.view(), 4, 2);
101+
assert_eq!(sampled_indices, array![0, 1, 5, 9]);
102+
}
65103

66-
// TODO
104+
#[test]
105+
fn test_many_random_runs_same_output() {
106+
let n = 20_000;
107+
for _ in 0..100 {
108+
let arr = get_array_f32(n);
109+
let idxs1 = minmaxlttb_scalar_without_x(arr.view(), 100, 5);
110+
let idxs2 = minmaxlttb_scalar_without_x_parallel(arr.view(), 100, 5);
111+
assert_eq!(idxs1, idxs2);
112+
}
113+
}
67114
}

0 commit comments

Comments
 (0)