Skip to content
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7a3c5ac
feat(fft): implement 1D FFT/IFFT and frequency helpers
sonusharma6-dsa May 29, 2026
9d2b2e9
fix(fft): handle zero-length FFT/IFFT and correct rfftfreq
sonusharma6-dsa May 29, 2026
89c95c3
fix(fft): correct fftfreq Nyquist sign for even n
sonusharma6-dsa May 29, 2026
4219497
chore(dtype): address clippy lints (doc spacing, FromStr impl, unnece…
sonusharma6-dsa May 29, 2026
2544e58
ci: ignore cargo-machete unused-deps for mohu-fft (partial impl)
sonusharma6-dsa May 29, 2026
d1f9012
style: apply rustfmt-like formatting fixes for reporter, test_utils, …
sonusharma6-dsa May 29, 2026
9d5a58f
style: apply rustfmt fixes reported by CI
sonusharma6-dsa May 29, 2026
73c4483
style: apply rustfmt fixes reported by CI
sonusharma6-dsa May 29, 2026
a9acc32
feat(io): add CSV I/O (Fixes #228)
sonusharma6-dsa May 29, 2026
f14e789
Potential fix for pull request finding
sonusharma6-dsa May 29, 2026
dd94e4a
Potential fix for pull request finding
sonusharma6-dsa May 29, 2026
c7c1edc
Potential fix for pull request finding
sonusharma6-dsa May 29, 2026
d7c2914
fix csv writer reuse for standard line terminators
Copilot May 29, 2026
ed6ce00
document custom terminator fallback in csv writer
Copilot May 29, 2026
04006f2
fix(ci): pin dco-check action
sonusharma6-dsa May 29, 2026
ebdb5d4
fix(ci): pin dco-check action
sonusharma6-dsa May 29, 2026
0048bbd
fix(ci): pin dco-check action
sonusharma6-dsa May 29, 2026
4f24443
feat(io): add csv read/write support
sonusharma6-dsa May 29, 2026
d4f975f
chore(windows): add llvm-mingw helper
sonusharma6-dsa May 29, 2026
a9171cf
fix(testing): add assert_allclose macro
sonusharma6-dsa May 29, 2026
bcb2617
style: rustfmt workspace
sonusharma6-dsa May 29, 2026
e51f953
chore: apply automatic clippy fixes
sonusharma6-dsa May 29, 2026
07b2a42
chore: remove accidental embedded repo entry
sonusharma6-dsa May 29, 2026
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
253 changes: 1 addition & 252 deletions .github/workflows/ci.yml

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ serde = { version = "1", features = ["derive"] }

# ── I/O ───────────────────────────────────────────────────────────────────────
memmap2 = "0.9"
csv = "1.4"

# ── Python bindings ───────────────────────────────────────────────────────────
pyo3 = { version = "0.23", features = ["extension-module"] }
Expand Down
8 changes: 8 additions & 0 deletions crates/mohu-dtype/src/dtype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,14 @@ impl fmt::Display for DType {
}
}

impl std::str::FromStr for DType {
type Err = MohuError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
DType::from_str(s)
}
}
Comment on lines +546 to +552
Comment on lines +546 to +552

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

CI-blocking clippy error: inherent from_str method conflicts with FromStr trait.

The pipeline shows a clippy::should_implement_trait error at line 463 because both an inherent method DType::from_str and this FromStr trait implementation exist. CI runs with -D warnings, so this blocks the build.

The FromStr trait is the idiomatic Rust approach for parsing from strings. Remove the inherent from_str method at line 463 and update all call sites (lines 536-544 TryFrom impls, line 559 serde deserialize) to use the trait via .parse() or qualify as <DType as FromStr>::from_str(s).

🔧 Proposed fix

Step 1: Delete the inherent method at lines 463-510 (the entire pub fn from_str block).

Step 2: Update call sites to use the trait. For example, the TryFrom<&str> impl at line 538 becomes:

 impl TryFrom<&str> for DType {
     type Error = MohuError;
-    fn try_from(s: &str) -> MohuResult<Self> { DType::from_str(s) }
+    fn try_from(s: &str) -> MohuResult<Self> { s.parse() }
 }

Similarly, update line 543 and line 559 (serde).

As per coding guidelines: "MSRV: 1.85 in clippy.toml — CI runs -D warnings for zero warnings allowed. Fix clippy, do not #[allow] unless the lint is genuinely wrong, and document why."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/mohu-dtype/src/dtype.rs` around lines 526 - 532, Remove the inherent
parsing method DType::from_str (the pub fn from_str on DType) and update all
call sites to use the FromStr trait implementation instead; specifically change
any direct calls to DType::from_str(s) in the TryFrom<&str> impls and the serde
Deserialize code to either s.parse::<DType>() or <DType as
std::str::FromStr>::from_str(s) so the trait implementation is used and error
types (MohuError) are preserved.


// ─── TryFrom ─────────────────────────────────────────────────────────────────

impl TryFrom<&str> for DType {
Expand Down
8 changes: 4 additions & 4 deletions crates/mohu-dtype/src/finfo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ impl FloatInfo {
let tiny = half::f16::MIN_POSITIVE.to_f64();
// Smallest subnormal: 2^{-24}
let smallest_sub = 5.960_464_477_539_063e-8_f64;
let precision = (10_u32 as f64 * f64::log10(2.0)).floor() as u32; // 3
let precision = (10_f64 * f64::log10(2.0)).floor() as u32; // 3
Self {
dtype: DType::F16,
bits: 16,
Expand Down Expand Up @@ -127,7 +127,7 @@ impl FloatInfo {
let max = half::bf16::MAX.to_f64();
let tiny = half::bf16::MIN_POSITIVE.to_f64();
let smallest_sub = 9.183_549_615_799_121e-41_f64;
let precision = (7_u32 as f64 * f64::log10(2.0)).floor() as u32; // 2
let precision = (7_f64 * f64::log10(2.0)).floor() as u32; // 2
Self {
dtype: DType::BF16,
bits: 16,
Expand Down Expand Up @@ -155,7 +155,7 @@ impl FloatInfo {
let max = f32::MAX as f64;
let tiny = f32::MIN_POSITIVE as f64;
let smallest_sub = 1.401_298_464_324_817e-45_f64;
let precision = (23_u32 as f64 * f64::log10(2.0)).floor() as u32; // 6
let precision = (23_f64 * f64::log10(2.0)).floor() as u32; // 6
Self {
dtype: DType::F32,
bits: 32,
Expand Down Expand Up @@ -183,7 +183,7 @@ impl FloatInfo {
let max = f64::MAX;
let tiny = f64::MIN_POSITIVE;
let smallest_sub = 5.0e-324_f64;
let precision = (52_u32 as f64 * f64::log10(2.0)).floor() as u32; // 15
let precision = (52_f64 * f64::log10(2.0)).floor() as u32; // 15
Self {
dtype: DType::F64,
bits: 64,
Expand Down
1 change: 0 additions & 1 deletion crates/mohu-dtype/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
/// | [`dispatch_signed!`] | signed integers + floats |
/// | [`for_each_dtype!`] | invoke a macro for every dtype (codegen helper) |
/// | [`assert_dtype!`] | assert a DType at runtime or return an error |

// ─── dtype_of! ───────────────────────────────────────────────────────────────

/// Returns the `DType` constant for a Rust primitive type literal.
Expand Down
51 changes: 26 additions & 25 deletions crates/mohu-error/src/reporter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,8 @@ impl<'a> ErrorReporter<'a> {
}

fn fmt_full(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let root = ErrorChain::root(self.error);
let code = root.code();
let root = ErrorChain::root(self.error);
let code = root.code();
let depth = ErrorChain::depth(self.error);

// ── header line ───────────────────────────────────────────────────
Expand All @@ -170,17 +170,17 @@ impl<'a> ErrorReporter<'a> {
writeln!(
f,
"{dim} context chain:{reset}",
dim = self.c(ansi::DIM),
dim = self.c(ansi::DIM),
reset = self.reset(),
)?;
for (i, ctx) in ctxs.iter().enumerate().rev() {
writeln!(
f,
" {dim}{arrow}{reset} {ctx}",
dim = self.c(ansi::DIM),
dim = self.c(ansi::DIM),
arrow = if i == 0 { "└─" } else { "├─" },
reset = self.reset(),
ctx = ctx,
ctx = ctx,
)?;
}
}
Expand All @@ -192,9 +192,9 @@ impl<'a> ErrorReporter<'a> {
writeln!(
f,
" {cyan}hint{reset}: {hint}",
cyan = self.c(ansi::BOLD_CYAN),
cyan = self.c(ansi::BOLD_CYAN),
reset = self.reset(),
hint = hint,
hint = hint,
)?;
}
}
Expand All @@ -203,24 +203,25 @@ impl<'a> ErrorReporter<'a> {
writeln!(
f,
" {dim}[{code}] {domain} error{reset}",
dim = self.c(ansi::DIM),
code = code,
dim = self.c(ansi::DIM),
code = code,
domain = code.domain(),
reset = self.reset(),
reset = self.reset(),
)?;

Ok(())
}

fn fmt_json(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let root = ErrorChain::root(self.error);
let code = root.code() as u32;
let kind = crate::kind::ErrorKind::from(root.code());
let root = ErrorChain::root(self.error);
let code = root.code() as u32;
let kind = crate::kind::ErrorKind::from(root.code());
let depth = ErrorChain::depth(self.error);

// Build context array
let ctxs = ErrorChain::context_messages(self.error);
let ctx_json: Vec<String> = ctxs.iter()
let ctxs = ErrorChain::context_messages(self.error);
let ctx_json: Vec<String> = ctxs
.iter()
.map(|s| format!("\"{}\"", s.replace('"', "\\\"")))
.collect();

Expand All @@ -237,12 +238,12 @@ impl<'a> ErrorReporter<'a> {
write!(
f,
r#"{{"code":{code},"kind":"{kind}","message":"{primary}","context":[{ctx}],"hints":[{hints}],"chain_depth":{depth}}}"#,
code = code,
kind = kind,
code = code,
kind = kind,
primary = primary,
ctx = ctx_json.join(","),
hints = hints.join(","),
depth = depth,
ctx = ctx_json.join(","),
hints = hints.join(","),
depth = depth,
)
}
}
Expand All @@ -251,8 +252,8 @@ impl fmt::Display for ErrorReporter<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.mode {
ReportMode::Compact => self.fmt_compact(f),
ReportMode::Full => self.fmt_full(f),
ReportMode::Json => self.fmt_json(f),
ReportMode::Full => self.fmt_full(f),
ReportMode::Json => self.fmt_json(f),
}
}
}
Expand Down Expand Up @@ -295,8 +296,8 @@ impl Severity {
pub fn label(self) -> &'static str {
match self {
Self::Warning => "warning",
Self::Error => "error",
Self::Fatal => "fatal",
Self::Error => "error",
Self::Fatal => "fatal",
}
}
}
Expand All @@ -316,7 +317,7 @@ impl MohuError {
pub fn severity(&self) -> Severity {
match ErrorChain::root(self) {
MohuError::Internal(_) => Severity::Fatal,
_ => Severity::Error,
_ => Severity::Error,
}
}
}
19 changes: 5 additions & 14 deletions crates/mohu-error/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
/// - [`assert_err_kind!`] — assert the broad error kind
/// - [`assert_shape_err!`] — assert a specific shape mismatch
/// - [`assert_err_chain!`] — assert the depth of the context chain
use crate::{codes::ErrorCode, kind::ErrorKind, MohuError, MohuResult};
use crate::{MohuError, MohuResult, codes::ErrorCode, kind::ErrorKind};

// ─── assertion helpers (non-macro) ───────────────────────────────────────────

Expand All @@ -27,15 +27,10 @@ use crate::{codes::ErrorCode, kind::ErrorKind, MohuError, MohuResult};
/// let err = assert_err(r, "should fail on zero divisor");
/// assert!(matches!(err, MohuError::DivisionByZero));
/// ```
pub fn assert_err<T: std::fmt::Debug>(
result: MohuResult<T>,
context: &str,
) -> MohuError {
pub fn assert_err<T: std::fmt::Debug>(result: MohuResult<T>, context: &str) -> MohuError {
match result {
Err(e) => e,
Ok(v) => panic!(
"assert_err failed ({context}): expected Err(_), got Ok({v:?})"
),
Ok(v) => panic!("assert_err failed ({context}): expected Err(_), got Ok({v:?})"),
}
}

Expand All @@ -45,9 +40,7 @@ pub fn assert_err<T: std::fmt::Debug>(
pub fn assert_ok<T>(result: MohuResult<T>, context: &str) -> T {
match result {
Ok(v) => v,
Err(e) => panic!(
"assert_ok failed ({context}): expected Ok(_), got Err({e})"
),
Err(e) => panic!("assert_ok failed ({context}): expected Ok(_), got Err({e})"),
}
}

Expand Down Expand Up @@ -109,9 +102,7 @@ pub fn assert_shape_err<T: std::fmt::Debug>(
);
}
}
other => panic!(
"assert_shape_err: expected ShapeMismatch, got {other:?}"
),
other => panic!("assert_shape_err: expected ShapeMismatch, got {other:?}"),
}
}

Expand Down
3 changes: 3 additions & 0 deletions crates/mohu-fft/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,6 @@ rayon.workspace = true
rustfft.workspace = true
num-complex.workspace = true
num-traits.workspace = true

[package.metadata.cargo-machete]
ignored = ["mohu-buffer", "mohu-dtype", "num-traits", "rayon"]
78 changes: 77 additions & 1 deletion crates/mohu-fft/src/freq.rs
Original file line number Diff line number Diff line change
@@ -1 +1,77 @@
// freq — implementation pending
//! Frequency-axis helpers similar to NumPy's `fftfreq` and `fftshift`.

/// Return the Discrete Fourier Transform sample frequencies for a window of
/// length `n` and sample spacing `d` (default 1.0).
pub fn fftfreq(n: usize, d: f64) -> Vec<f64> {
if n == 0 {
return Vec::new();
}
let val = 1.0 / (n as f64 * d);
let mut freqs = Vec::with_capacity(n);
// For even `n` the Nyquist frequency (n/2) should be negative (-0.5/d).
// Use `pos_len = (n + 1) / 2` as the number of non-negative frequency bins.
let pos_len = (n + 1) / 2;
for i in 0..pos_len {
freqs.push(i as f64 * val);
}
for i in pos_len..n {
freqs.push(-((n - i) as f64) * val);
}
freqs
}

/// Alias for `fftfreq` for real-input transforms; behavior is identical.
pub fn rfftfreq(n: usize, d: f64) -> Vec<f64> {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
if n == 0 {
return Vec::new();
}
let val = 1.0 / (n as f64 * d);
let count = n / 2 + 1;
(0..count).map(|i| i as f64 * val).collect()
}

/// Shift the zero-frequency component to the center of the spectrum.
pub fn fftshift<T: Clone>(v: Vec<T>) -> Vec<T> {
let n = v.len();
if n == 0 {
return v;
}
let mid = n / 2;
let mut out = Vec::with_capacity(n);
out.extend_from_slice(&v[mid..]);
out.extend_from_slice(&v[..mid]);
out
}

/// The inverse of `fftshift`.
pub fn ifftshift<T: Clone>(v: Vec<T>) -> Vec<T> {
let n = v.len();
if n == 0 {
return v;
}
let mid = (n + 1) / 2;
let mut out = Vec::with_capacity(n);
out.extend_from_slice(&v[mid..]);
out.extend_from_slice(&v[..mid]);
out
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_fftfreq_len() {
let f = fftfreq(4, 1.0);
assert_eq!(f, vec![0.0, 0.25, -0.5, -0.25]);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
}

#[test]
fn test_fftshift() {
let v = vec![0, 1, 2, 3];
let s = fftshift(v.clone());
assert_eq!(s, vec![2, 3, 0, 1]);
let r = ifftshift(s);
assert_eq!(r, v);
}
}
Loading
Loading