Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
378daa2
Add kaiser_atten
SpookyYomo Oct 16, 2024
d2c78ec
Add kaiser_beta
SpookyYomo Oct 17, 2024
7fdef5c
Add kaiserord
SpookyYomo Oct 18, 2024
0025daf
Add signal::windows namespace and helper functions
SpookyYomo Oct 22, 2024
1a2feb6
Add Boxcar window type
SpookyYomo Oct 23, 2024
e860413
Add triangle window
SpookyYomo Oct 25, 2024
87384f4
Add general cosine window
SpookyYomo Nov 19, 2024
b3b4ff9
Add Blackman window
SpookyYomo Nov 22, 2024
e5033da
Add more documentation to signal module
SpookyYomo Nov 23, 2024
35aad91
Add general gaussian window
SpookyYomo Nov 24, 2024
575e428
Add General Hamming
SpookyYomo Nov 25, 2024
a052c20
Add Hamming window
SpookyYomo Nov 26, 2024
4696efc
Add kaiser window
SpookyYomo Nov 28, 2024
fe309d3
Add library level errors
SpookyYomo Nov 29, 2024
ca2a064
Add Nuttall window
SpookyYomo Dec 3, 2024
cd9b27d
Add convenience GetWindow impl for Window variants
SpookyYomo Dec 4, 2024
df21ea3
Add scipy.signal.windows.get_window function
SpookyYomo Dec 4, 2024
fe8c79a
Add firwin
SpookyYomo Dec 6, 2024
bff6c9d
Fix lack of use of num_traits::identities
SpookyYomo Dec 9, 2024
dbf32f7
Add error enums
SpookyYomo Mar 11, 2025
66d829a
Add non-allocating variants of Error enum variants
SpookyYomo Mar 11, 2025
42c2903
Add ndarray_conv to core
SpookyYomo Apr 25, 2025
54e75e4
Add Result "alias" to core
SpookyYomo Apr 25, 2025
2d1f079
Add error messages
SpookyYomo Apr 25, 2025
333c992
Add error messages arising from ndarray_conv
SpookyYomo Apr 26, 2025
bfcbc37
Add linear convolution through ndarray_conv in num-rs space
SpookyYomo Apr 27, 2025
ea43d53
Change np-convolve to use ArrayView instead of ArrayBase in the 1st arg
SpookyYomo Apr 28, 2025
aee705d
Remove kernel vs signal debug assertion
SpookyYomo Apr 28, 2025
1b484a0
Add sci-rs-core Cargo.toml
SpookyYomo Apr 29, 2025
5fbca6b
Merge remote-tracking branch 'origin/core' into np-convolve
SpookyYomo Apr 29, 2025
fc25134
Build sci-rs-core with features used to build sci-rs
SpookyYomo Apr 29, 2025
6ff0282
Change lfilter to return an error if axis argument is invalid
SpookyYomo Jun 11, 2025
391cdc7
Remove unnecessary reborrow
SpookyYomo Jun 12, 2025
90b0bd4
Assert that lfilter arguments are not 0-dimensional arrays
SpookyYomo Jun 13, 2025
f21a6bc
Update ndarray-conv to 0.5.0 in core
SpookyYomo Jul 6, 2025
8d1ae35
Merge branch 'core' into np-convolve
SpookyYomo Jul 7, 2025
182658d
Fix linear convolve behaviour
SpookyYomo Jul 14, 2025
54fc05f
Add initial lfilter implementation for 1d
SpookyYomo Apr 30, 2025
c6734de
Remove unnecessary cfg parameter
SpookyYomo Jul 15, 2025
6a456de
Fix clippy errors in Triangle
SpookyYomo Jul 15, 2025
fc31076
Add more lfilter tests in the context of 1-dimensional input without zi
SpookyYomo Jul 15, 2025
f5e6579
Refactor Axis yielding logic
SpookyYomo Jul 16, 2025
a6c22f7
Include linear_filter function
SpookyYomo Jul 17, 2025
c6d4504
Improve internal function name and documentation
SpookyYomo Jul 18, 2025
a33bf92
Partially undo refactor to optimize `zi.is_none()` path
SpookyYomo Jul 23, 2025
b8954e5
Propagate error from convolve instead of unwrap within lfilter
SpookyYomo Jul 24, 2025
f0c971a
Introduce LFilter as trait instead
SpookyYomo Jul 25, 2025
301b779
Move lfilter implementation into macro
SpookyYomo Jul 28, 2025
8d99c3f
Add happy zi.is_some() case
SpookyYomo Jul 30, 2025
47be4c4
Implement path that handles the unexpected shape of zi
SpookyYomo Aug 11, 2025
f7d2c68
Touch up lfilter documentation
SpookyYomo Aug 12, 2025
c2cd592
Change lfilter_zi documentation to point to new lfilter implementation
SpookyYomo Aug 13, 2025
396887a
Remove unnecessary heap allocation of zi
SpookyYomo Aug 15, 2025
fd097e5
Update dev-dependency `rand` to 0.9.2
SpookyYomo Sep 19, 2025
b905fa6
Change rand to use newer functions over deprecated functions
SpookyYomo Sep 20, 2025
7a11fb8
Add bench for lfilter for order 500
SpookyYomo Sep 22, 2025
7760754
fix: Add zero numtap check in Firwin validation
SpookyYomo Sep 23, 2025
4aaa73a
Fix up docstring in lfilter
SpookyYomo Sep 23, 2025
279cc09
Prepare for IxDyn compatible lfilter function
SpookyYomo Oct 2, 2025
207c625
Add IxDyn compatible lfilter
SpookyYomo Oct 3, 2025
8fb1e52
Remove useless conversion of Vec<Ix> in lfilter
SpookyYomo Oct 4, 2025
ae13a03
Provision types for lfilter return types
SpookyYomo Oct 5, 2025
a3b6b54
Change LFilter trait bounds
SpookyYomo Oct 18, 2025
4cd1042
Move `lfilter::ndarray_shape_...` to `arraytools::..._st`
SpookyYomo Oct 18, 2025
ab84226
Change zeroing to direct heap-reservation for lfilter intermediates
SpookyYomo Oct 24, 2025
ef26b6c
Add const inlining of user's `axis: Option<isize>` to `usize`
SpookyYomo Oct 24, 2025
952223d
Change lfilter(for IxDyn arrays) to use get_axis_dyn
SpookyYomo Oct 25, 2025
61d4392
Change check_and_get_axis_* to return only `usize`
SpookyYomo Oct 26, 2025
07ba98a
Move `lfilter::check_and_get_axis*` to `arraytools::`
SpookyYomo Oct 27, 2025
d100c40
Remove unnecessary trait requirements of LFilter
SpookyYomo Oct 28, 2025
5b112b3
Reduce bench for lfilter to decimation factor of 50 instead
SpookyYomo Oct 29, 2025
7489308
Change rand imports in plot-test
SpookyYomo Nov 16, 2025
3be91e4
Merge remote-tracking branch 'origin/upd_rand' into lfilter_bench
SpookyYomo Sep 21, 2025
c300bc8
Fix improper error message from window/width both being some
SpookyYomo Nov 17, 2025
98998e8
Merge remote-tracking branch 'origin/firwin' into lfilter_bench
SpookyYomo Sep 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions sci-rs-core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[package]
name = "sci-rs-core"
version = "0.0.0"
edition = "2021"
authors = ["Jacob Trueb <[email protected]>"]
description = "Core library for sci-rs internals."
license = "MIT OR Apache-2.0"
repository = "https://github.com/qsib-cbie/sci-rs.git"
homepage = "https://github.com/qsib-cbie/sci-rs.git"
readme = "../README.md"
keywords = ["scipy", "dsp", "signal", "filter", "design"]
categories = ["science", "mathematics", "no-std", "embedded"]


[package.metadata.docs.rs]
all-features = true

[features]
default = ['alloc']

# Allow allocating vecs, matrices, etc.
alloc = []

# Enable FFT and standard library features
std = ['alloc']

[dependencies]
ndarray = { version = "0.16.1", default-features = false }
ndarray-conv = { version = "0.5.0" }
num-traits = { version = "0.2.15", default-features = false }
79 changes: 79 additions & 0 deletions sci-rs-core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//! Core library for sci-rs.

#![cfg_attr(not(feature = "std"), no_std)]

#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::format;

use core::{error, fmt};

pub type Result<T> = core::result::Result<T, Error>;

/// Errors raised whilst running sci-rs.
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
/// Argument parsed into function were invalid.
#[cfg(feature = "alloc")]
InvalidArg {
/// The invalid arg
arg: alloc::string::String,
/// Explaining why arg is invalid.
reason: alloc::string::String,
},
/// Argument parsed into function were invalid.
#[cfg(not(feature = "alloc"))]
InvalidArg,
/// Two or more optional arguments passed into functions conflict.
#[cfg(feature = "alloc")]
ConflictArg {
/// Explaining what arg is invalid.
reason: alloc::string::String,
},
/// Two or more optional arguments passed into functions conflict.
#[cfg(not(feature = "alloc"))]
ConflictArg,
/// Errors raised by [ndarray_conv::Error]
#[cfg(feature = "alloc")]
Conv { reason: alloc::string::String },
/// Errors raised by [ndarray_conv::Error]
#[cfg(not(feature = "alloc"))]
Conv,
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
#[cfg(feature = "alloc")]
Error::InvalidArg { arg, reason } =>
format!("Invalid Argument on arg = {} with reason = {}", arg, reason),
#[cfg(not(feature = "alloc"))]
Error::InvalidArg =>
"There were invalid arguments. Reasons not shown without `alloc` feature.",
#[cfg(feature = "alloc")]
Error::ConflictArg { reason } =>
format!("Conflicting Arguments with reason = {}", reason),
#[cfg(not(feature = "alloc"))]
Error::ConflictArg =>
"There were conflicting arguments. Reasons not shown without `alloc` feature.",
#[cfg(feature = "alloc")]
Error::Conv { reason } => format!(
"An error occurred during the convolution from ndarray_conv with reason {}.",
reason
),
#[cfg(not(feature = "alloc"))]
Error::Conv => "An error occurred during the convolution from ndarray_conv. Reasons not shown without `alloc` feature.",
}
)
}
}

impl error::Error for Error {}

/// Collection of numpy-like functions for use by sci-rs.
/// Provide behaviour parity against Numpy, even if the types are not identical.
pub mod num_rs;
135 changes: 135 additions & 0 deletions sci-rs-core/src/num_rs/convolve/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
mod ndarray_conv_binds;

use crate::{Error, Result};
use alloc::string::ToString;
use ndarray::{Array1, ArrayView1};
use ndarray_conv::{ConvExt, PaddingMode};

/// Convolution mode determines behavior near edges and output size
pub enum ConvolveMode {
/// Full convolution, output size is `in1.len() + in2.len() - 1`
Full,
/// Valid convolution, output size is `max(in1.len(), in2.len()) - min(in1.len(), in2.len()) + 1`
Valid,
/// Same convolution, output size is `in1.len()`
Same,
}

/// Best effort parallel behaviour with numpy's convolve method. We take `v` as the convolution
/// kernel.
///
/// Returns the discrete, linear convolution of two one-dimensional sequences.
///
/// # Parameters
/// * `a` : (N,) [[array_like]]([ndarray::Array1])
/// Signal to be (linearly) convolved.
/// * `v` : (M,) [[array_like]]([ndarray::Array1])
/// Second one-dimensional input array.
/// * `mode` : [ConvolveMode]
/// [ConvolveMode::Full]:
/// By default, mode is 'full'. This returns the convolution at each point of overlap, with an
/// output shape of (N+M-1,). At the end-points of the convolution, the signals do not overlap
/// completely, and boundary effects may be seen.
///
/// [ConvolveMode::Same]:
/// Mode 'same' returns output of length ``max(M, N)``. Boundary effects are still visible.
///
/// [ConvolveMode::Valid]:
/// Mode 'valid' returns output of length ``max(M, N) - min(M, N) + 1``. The convolution
/// product is only given for points where the signals overlap completely. Values outside the
/// signal boundary have no effect.
///
/// # Panics
/// We assume that `v` is shorter than `a`.
///
/// # Examples
/// With [ConvolveMode::Full]:
/// ```
/// use ndarray::array;
/// use sci_rs_core::num_rs::{ConvolveMode, convolve};
///
/// let a = array![1., 2., 3.];
/// let v = array![0., 1., 0.5];
///
/// let expected = array![0., 1., 2.5, 4., 1.5];
/// let result = convolve((&a).into(), (&v).into(), ConvolveMode::Full).unwrap();
/// assert_eq!(result, expected);
/// ```
/// With [ConvolveMode::Same]:
/// ```
/// use ndarray::array;
/// use sci_rs_core::num_rs::{ConvolveMode, convolve};
///
/// let a = array![1., 2., 3.];
/// let v = array![0., 1., 0.5];
///
/// let expected = array![1., 2.5, 4.];
/// let result = convolve((&a).into(), (&v).into(), ConvolveMode::Same).unwrap();
/// assert_eq!(result, expected);
/// ```
/// With [ConvolveMode::Same]:
/// ```
/// use ndarray::array;
/// use sci_rs_core::num_rs::{ConvolveMode, convolve};
///
/// let a = array![1., 2., 3.];
/// let v = array![0., 1., 0.5];
///
/// let expected = array![2.5];
/// let result = convolve((&a).into(), (&v).into(), ConvolveMode::Valid).unwrap();
/// assert_eq!(result, expected);
/// ```
pub fn convolve<T>(a: ArrayView1<T>, v: ArrayView1<T>, mode: ConvolveMode) -> Result<Array1<T>>
where
T: num_traits::NumAssign + core::marker::Copy,
{
// Convolve
let result = a.conv(&v, mode.into(), PaddingMode::Zeros);
#[cfg(feature = "alloc")]
{
result.map_err(|e| Error::Conv {
reason: e.to_string(),
})
}
#[cfg(not(feature = "alloc"))]
{
result.map_err({ Error::Conv })
}
}

#[cfg(test)]
mod linear_convolve {
use super::*;
use alloc::vec;
use ndarray::array;

#[test]
fn full() {
let a = array![1., 2., 3.];
let v = array![0., 1., 0.5];

let expected = array![0., 1., 2.5, 4., 1.5];
let result = convolve((&a).into(), (&v).into(), ConvolveMode::Full).unwrap();
assert_eq!(result, expected);
}

#[test]
fn same() {
let a = array![1., 2., 3.];
let v = array![0., 1., 0.5];

let expected = array![1., 2.5, 4.];
let result = convolve((&a).into(), (&v).into(), ConvolveMode::Same).unwrap();
assert_eq!(result, expected);
}

#[test]
fn valid() {
let a = array![1., 2., 3.];
let v = array![0., 1., 0.5];

let expected = array![2.5];
let result = convolve((&a).into(), (&v).into(), ConvolveMode::Valid).unwrap();
assert_eq!(result, expected);
}
}
12 changes: 12 additions & 0 deletions sci-rs-core/src/num_rs/convolve/ndarray_conv_binds.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use super::ConvolveMode;
use ndarray_conv::ConvMode;

impl<const N: usize> From<ConvolveMode> for ConvMode<N> {
fn from(value: ConvolveMode) -> Self {
match value {
ConvolveMode::Full => ConvMode::Full,
ConvolveMode::Same => ConvMode::Same,
ConvolveMode::Valid => ConvMode::Valid,
}
}
}
4 changes: 4 additions & 0 deletions sci-rs-core/src/num_rs/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#[cfg(feature = "alloc")]
mod convolve;
#[cfg(feature = "alloc")]
pub use convolve::*;
11 changes: 8 additions & 3 deletions sci-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ all-features = true
default = ['alloc']

# Allow allocating vecs, matrices, etc.
alloc = ['nalgebra/alloc', 'nalgebra/libm', 'kalmanfilt/alloc']
alloc = ['nalgebra/alloc', 'nalgebra/libm', 'kalmanfilt/alloc', 'sci-rs-core/alloc']

# Enable FFT and standard library features
std = ['nalgebra/std', 'nalgebra/macros', 'rustfft', 'alloc']
std = ['nalgebra/std', 'nalgebra/macros', 'rustfft', 'alloc','sci-rs-core/std']

# Enable debug plotting through python system calls
plot = ['std']
Expand All @@ -36,12 +36,13 @@ lstsq = { version = "0.6.0", default-features = false }
rustfft = { version = "6.2.0", optional = true }
kalmanfilt = { version = "0.3.0", default-features = false }
gaussfilt = { version = "0.1.3", default-features = false }
sci-rs-core = { path = "../sci-rs-core", default-features = false }

[dev-dependencies]
approx = "0.5.1"
dasp_signal = { version = "0.11.0" }
criterion = { version = "0.4", features = ["html_reports"] }
rand = "0.8.4"
rand = "0.9.2"

[[bench]]
name = "sosfilt"
Expand All @@ -50,3 +51,7 @@ harness = false
[[bench]]
name = "sosfiltfilt"
harness = false

[[bench]]
name = "lfilter"
harness = false
Loading
Loading