Skip to content

feat(index, random): implement index_bool, index_take, and Rng struct - #263

Open
rajesh-puripanda wants to merge 2 commits into
mohu-org:mainfrom
rajesh-puripanda:feat/index-bool-take
Open

feat(index, random): implement index_bool, index_take, and Rng struct#263
rajesh-puripanda wants to merge 2 commits into
mohu-org:mainfrom
rajesh-puripanda:feat/index-bool-take

Conversation

@rajesh-puripanda

@rajesh-puripanda rajesh-puripanda commented May 31, 2026

Copy link
Copy Markdown

Implements two issues:

#149 — Advanced indexing

\index_bool(src, mask) -> Buffer\

Boolean mask filtering — returns 1D buffer of elements where mask is true.

\index_take(src, indices, axis) -> Buffer\

Integer array indexing along an axis (NumPy's \ ake). Supports negative indices and bounds checking.

#140 — Random number generator

\Rng\ struct


  • ew(seed: u64)\ — seeded PCG-64-DXSM engine
  • \uniform(shape, low, high)\ — f64 uniform in [low, high)\

  • ormal(shape, mean, std)\ — f64 Gaussian via Box-Muller
  • \integers(shape, low, high)\ — i64 uniform in [low, high)\

Both use safe Rust only, with \dispatch_dtype!\ for runtime type dispatch (index) and direct engine usage (Rng). Includes comprehensive tests for edge cases, reproducibility, and error handling.

Closes #149
Closes #140

Add boolean mask indexing (index_bool) and integer array indexing
along an axis (index_take) to the mohu-index crate.

index_bool filters a buffer by a boolean mask, returning a 1D buffer
of selected elements. Supports all dtypes via dispatch_dtype.

index_take selects elements along a given axis using an array of
integer indices, with support for negative indices and bounds
checking. Uses dispatch_dtype for runtime type dispatch.

Both functions are fully safe (no unsafe code) and include
comprehensive tests.

Signed-off-by: rajesh-puripanda <rajesh.puripanda@icloud.com>
@github-actions

Copy link
Copy Markdown

PR Check Summary

Item Value
Branch feat/index-bool-take
Changed crates crates/mohu-index
Files changed 2

CI will run: build, test, clippy, fmt, cargo-deny, DCO, semver.
Reviewer assigned from CODEOWNERS.

@coderabbitai

coderabbitai Bot commented May 31, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR implements two advanced indexing functions for mohu-index: index_bool for boolean mask selection and index_take for integer array indexing along a specified axis. Both functions validate inputs, dispatch operations by dtype, and return owning buffers with comprehensive test coverage.

Changes

Advanced Indexing API

Layer / File(s) Summary
Boolean mask indexing implementation and tests
crates/mohu-index/src/boolean.rs
index_bool validates that mask is boolean dtype and matches src shape, collects coordinates where mask is true, allocates output 1D buffer sized to true count, copies matching elements via dispatch_dtype!, and returns empty buffer when no matches exist. Tests cover 1D and 2D cases, all-true, all-false, and dtype/shape error scenarios.
Integer array indexing with axis selection
crates/mohu-index/src/take.rs
index_take validates inputs (ndim, axis, I64 indices), wraps negative indices, handles empty sources by returning zero buffer with axis dimension 0, allocates output with shape matching src except axis dimension replaced by indices count, iterates reduced-dimension coordinates and populates output via dispatch_dtype! element access. Tests cover 1D/2D cases across axes, negative index wrapping, empty indices, out-of-bounds errors, dtype validation, and invalid axis detection.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🐰 Two indexing paths now blazed,
Masks and integers, deftly grazed,
Shape-checked, dispatch-cast,
From first to last—
Advanced selection, clearly phased! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title mentions 'index_bool' and 'index_take' which are the primary changes, but also includes 'Rng struct' which is not present in the changeset—making it partially inaccurate. Update the title to 'feat(index): implement index_bool and index_take' to accurately reflect only the changes in this PR, removing the unimplemented 'Rng struct' reference.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR implements both required functions (index_bool and index_take) with all specified error handling, constraints, and comprehensive test coverage matching issue #149 requirements.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the two indexing functions specified in issue #149; no out-of-scope modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Add convenience Rng struct wrapping Pcg64 with three sampling methods:
- uniform: f64 values in [low, high)
- normal: Gaussian variates via Box-Muller (all dtypes via dispatch)
- integers: i64 values in [low, high)

Includes reproducibility guarantee, domain error validation,
and comprehensive tests for all methods.

Signed-off-by: rajesh-puripanda <rajesh.puripanda@icloud.com>
@rajesh-puripanda rajesh-puripanda changed the title feat(index): implement index_bool and index_take feat(index, random): implement index_bool, index_take, and Rng struct May 31, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
crates/mohu-index/src/boolean.rs (1)

30-50: ⚖️ Poor tradeoff

Eliminate per-true Vec<usize> allocations in crates/mohu-index/src/boolean.rs

true_coords: Vec<Vec<usize>> plus coord.to_vec() heap-allocates one Vec per selected element. mohu-buffer::Buffer exposes get/set(&[usize]) (no linear/offset get/set), so the simplest allocation-free refactor is a two-pass walk: first count true elements to size out, then re-iterate the indices and copy matching src values into out directly (without storing coordinates).

🤖 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-index/src/boolean.rs` around lines 30 - 50, The current
implementation builds true_coords: Vec<Vec<usize>> by pushing coord.to_vec() for
every true mask which allocates per-element; instead, do a two-pass iteration
over NdIndexIter::new(src.shape()): first pass just count
mask.get::<bool>(&coord)? to compute out_len and allocate out via
Buffer::zeros(src.dtype(), &[out_len])?, return early if zero; second pass
iterate again and maintain a single usize write_idx counter that increments for
each true mask and directly call src.get::<T>(&coord)? and
out.set::<T>(&[write_idx], val)? (adjust the copy_bool! macro to use the
write_idx counter instead of indexing into true_coords). Remove true_coords and
coord.to_vec() usage entirely.
🤖 Prompt for all review comments with 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.

Inline comments:
In `@crates/mohu-index/src/boolean.rs`:
- Around line 82-88: The test test_index_bool_all_true currently builds a
Buffer<f64> and compares slices with assert_eq, but
mohu_testing::approx::assert_allclose only accepts &[f32]; change the test to
use Buffer::from_slice::<f32> for src, produce the mask as before, call
index_bool(&src, &mask).unwrap(), then call
mohu_testing::approx::assert_allclose on result.as_slice::<f32>().unwrap() with
the expected &[1.0_f32, 2.0_f32, 3.0_f32]; keep the test name and use the same
Buffer and index_bool symbols so only the element type and assertion change.
- Around line 17-28: Replace the manual early-returns in the function
index_bool: instead of the explicit if checks that call return Err(...), use the
mohu-error ensure! macro (ensure!(mask.dtype() == DType::Bool,
MohuError::DomainError { ... }) and ensure!(mask.shape() == src.shape(),
MohuError::BoolIndexShapeMismatch { ... })), preserving the same error
constructors; also update the test test_index_bool_all_true to use f32 data and
compare using moh u_testing::approx::assert_allclose (switch the slice type from
f64 to f32 and call assert_allclose on the resulting &[f32]) so the approximate
comparison helper is used.

In `@crates/mohu-index/src/take.rs`:
- Around line 67-71: The branch that handles empty-source currently sets
out_shape[axis] = 0 which is incorrect when the source is empty due to a
non-axis dimension; instead set out_shape[axis] = num_indices so the output
shape matches NumPy take semantics (works for both src.len() == 0 and
num_indices == 0). Update the code around the if checking num_indices == 0 ||
src.len() == 0 in take.rs to assign out_shape[axis] = num_indices before
returning Buffer::zeros(src.dtype(), &out_shape).
- Around line 17-36: The error handling in index_take needs to use the project's
ensure!/bail! macros and construct AxisOutOfRange with its required valid
string: replace the explicit returns of Err(MohuError::TooManyIndices { .. }),
Err(MohuError::AxisOutOfRange { .. }), and Err(MohuError::DomainError { .. }) in
the function index_take with ensure! or bail! calls (e.g., ensure!(ndim != 0,
TooManyIndices{...}) or bail!(DomainError{...}) as appropriate), and when
constructing MohuError::AxisOutOfRange include the missing valid: String field
(for example "0..ndim") so the variant is fully initialized; also verify the
empty-input/output shape handling remains correct and update any tests that
compare floats to use mohu_testing::approx::assert_allclose instead of exact
equality.

---

Nitpick comments:
In `@crates/mohu-index/src/boolean.rs`:
- Around line 30-50: The current implementation builds true_coords:
Vec<Vec<usize>> by pushing coord.to_vec() for every true mask which allocates
per-element; instead, do a two-pass iteration over
NdIndexIter::new(src.shape()): first pass just count mask.get::<bool>(&coord)?
to compute out_len and allocate out via Buffer::zeros(src.dtype(), &[out_len])?,
return early if zero; second pass iterate again and maintain a single usize
write_idx counter that increments for each true mask and directly call
src.get::<T>(&coord)? and out.set::<T>(&[write_idx], val)? (adjust the
copy_bool! macro to use the write_idx counter instead of indexing into
true_coords). Remove true_coords and coord.to_vec() usage entirely.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1bfdb847-85eb-46f6-b69e-dc10e3590019

📥 Commits

Reviewing files that changed from the base of the PR and between 0afc975 and 1a232b1.

📒 Files selected for processing (2)
  • crates/mohu-index/src/boolean.rs
  • crates/mohu-index/src/take.rs

Comment thread crates/mohu-index/src/boolean.rs
Comment thread crates/mohu-index/src/boolean.rs
Comment on lines +17 to +36
pub fn index_take(src: &Buffer, indices: &Buffer, axis: usize) -> MohuResult<Buffer> {
let ndim = src.ndim();
if ndim == 0 {
return Err(MohuError::TooManyIndices {
given: 1,
ndim: 0,
});
}
if axis >= ndim {
return Err(MohuError::AxisOutOfRange {
axis: axis as i64,
ndim,
});
}
if indices.ndim() != 1 || indices.dtype() != DType::I64 {
return Err(MohuError::DomainError {
op: "index_take",
reason: "indices must be a 1D I64 array".into(),
});
}

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
ast-grep --pattern 'pub enum MohuError { $$$ }'
rg -nP '\b(FancyIndexOutOfBounds|TooManyIndices|AxisOutOfRange|IndexOutOfBounds)\b' --type=rust -g '*error*' -C1

Repository: mohu-org/mohu

Length of output: 37948


Fix index_take error construction + use ensure!/bail! for guards

  • In crates/mohu-index/src/take.rs, MohuError::AxisOutOfRange must include the required valid: String field (the current AxisOutOfRange { axis: ..., ndim: ... } construction omits it).
  • Replace the guard/out-of-bounds return Err(MohuError::...) paths with ensure!/bail! per the project error-handling rules.
  • Double-check the empty-input/output shape handling and update any float equality assertions in tests to use mohu_testing::approx::assert_allclose.
🤖 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-index/src/take.rs` around lines 17 - 36, The error handling in
index_take needs to use the project's ensure!/bail! macros and construct
AxisOutOfRange with its required valid string: replace the explicit returns of
Err(MohuError::TooManyIndices { .. }), Err(MohuError::AxisOutOfRange { .. }),
and Err(MohuError::DomainError { .. }) in the function index_take with ensure!
or bail! calls (e.g., ensure!(ndim != 0, TooManyIndices{...}) or
bail!(DomainError{...}) as appropriate), and when constructing
MohuError::AxisOutOfRange include the missing valid: String field (for example
"0..ndim") so the variant is fully initialized; also verify the
empty-input/output shape handling remains correct and update any tests that
compare floats to use mohu_testing::approx::assert_allclose instead of exact
equality.

Comment on lines +67 to +71
if num_indices == 0 || src.len() == 0 {
let mut out_shape: Vec<usize> = src.shape().to_vec();
out_shape[axis] = 0;
return Buffer::zeros(src.dtype(), &out_shape);
}

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 | 🟡 Minor | ⚡ Quick win

Empty-source path produces wrong output shape along the axis.

When src.len() == 0 because a non-axis dimension is zero (e.g. shape [0, 3], axis = 1, indices = [0, 1]), the bounds checks pass but this branch forces out_shape[axis] = 0, yielding [0, 0] instead of the expected [0, num_indices] (NumPy take semantics). Using num_indices also stays correct for the num_indices == 0 case.

🐛 Proposed fix
     if num_indices == 0 || src.len() == 0 {
         let mut out_shape: Vec<usize> = src.shape().to_vec();
-        out_shape[axis] = 0;
+        out_shape[axis] = num_indices;
         return Buffer::zeros(src.dtype(), &out_shape);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if num_indices == 0 || src.len() == 0 {
let mut out_shape: Vec<usize> = src.shape().to_vec();
out_shape[axis] = 0;
return Buffer::zeros(src.dtype(), &out_shape);
}
if num_indices == 0 || src.len() == 0 {
let mut out_shape: Vec<usize> = src.shape().to_vec();
out_shape[axis] = num_indices;
return Buffer::zeros(src.dtype(), &out_shape);
}
🤖 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-index/src/take.rs` around lines 67 - 71, The branch that handles
empty-source currently sets out_shape[axis] = 0 which is incorrect when the
source is empty due to a non-axis dimension; instead set out_shape[axis] =
num_indices so the output shape matches NumPy take semantics (works for both
src.len() == 0 and num_indices == 0). Update the code around the if checking
num_indices == 0 || src.len() == 0 in take.rs to assign out_shape[axis] =
num_indices before returning Buffer::zeros(src.dtype(), &out_shape).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement advanced indexing (boolean mask, integer array) in mohu-index Implement random number generator and sampling in mohu-random

1 participant