Skip to content

Apply new validation to metrics.pairwise_distances - #8065

Merged
rapids-bot[bot] merged 7 commits into
NVIDIA:mainfrom
csadorf:issue-7998-apply-new-validation-to-metrics-pairwise-distances
May 7, 2026
Merged

Apply new validation to metrics.pairwise_distances#8065
rapids-bot[bot] merged 7 commits into
NVIDIA:mainfrom
csadorf:issue-7998-apply-new-validation-to-metrics-pairwise-distances

Conversation

@csadorf

@csadorf csadorf commented May 7, 2026

Copy link
Copy Markdown
Contributor

Convert the dense paths in cuml.metrics.pairwise_distances and nan_euclidean_distances to the new check_array validation flow, including layout handling and coverage for non-finite inputs and nan_euclidean behavior.

Part of #7998.

@csadorf
csadorf requested a review from a team as a code owner May 7, 2026 14:22
@csadorf csadorf added improvement Improvement / enhancement to an existing function non-breaking Non-breaking change labels May 7, 2026
@csadorf
csadorf requested a review from betatim May 7, 2026 14:22
@github-actions github-actions Bot added the Cython / Python Cython or Python issue label May 7, 2026
@coderabbitai

coderabbitai Bot commented May 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added support for a NaN-aware Euclidean metric that handles NaN values in distance calculations.
  • Bug Fixes

    • Improved input validation and error handling for pairwise distance functions; standard distances now reject non-finite values.
    • Dtype-mismatch now raises ValueError for consistency.
    • Fixed layout/regression issues for single-sample inputs so results match expectations across memory layouts.
  • Documentation

    • Clarified accepted input formats (including CUDA array-interface compliant arrays).

Walkthrough

This PR migrates pairwise distance metrics (pairwise_distances and nan_euclidean_distances) from input_to_cuml_array to check_array for input validation. The changes introduce a degenerate-dimension heuristic for memory order selection when Y is provided and X has single sample or feature dimensions, and extend test coverage for non-finite handling and layout combinations.

Changes

Pairwise Metrics Input Validation Refactor

Layer / File(s) Summary
Dependencies
python/cuml/cuml/metrics/pairwise_distances.pyx
Replace input_to_cuml_array import with explicit check_array and CumlArray imports.
API Contracts
python/cuml/cuml/metrics/pairwise_distances.pyx, python/cuml/cuml/metrics/pairwise_kernels.py
Docstrings updated for pairwise_distances, nan_euclidean_distances, and pairwise_kernels to document accepted input formats and parameter behavior with cuda array interface details.
nan_euclidean_distances Implementation
python/cuml/cuml/metrics/pairwise_distances.pyx
Input validation refactored to use check_array with float32/float64 dtype constraints and ensure_all_finite=False; Y memory order inferred from X_m contiguity.
pairwise_distances Dense Path
python/cuml/cuml/metrics/pairwise_distances.pyx
Dense path rewritten to validate/convert using check_array, compute is_row_major from X_m contiguity, apply metric-specific boolean conversion for russellrao, and select Y layout via degenerate-dimension heuristic (when n_samples_x == 1 or n_features_x == 1) or forced matching. Metric-to-enum mapping and C++ dispatch updated to use new dimension variables.
Test Coverage
python/cuml/tests/test_metrics.py
Import nan_euclidean_distances; fix exception type from TypeError to ValueError for mismatched dtype; add tests for non-finite rejection, nan_euclidean allowance, NaN diagonal correctness, and degenerate input layout combinations.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • rapidsai/cuml#8019: Both PRs perform the same input-validation migration (replacing input_to_cuml_array/check_features with check_array/check_inputs and switching pointer access to .data.ptr), so they are related.

Suggested labels

Cython / Python

Suggested reviewers

  • betatim
  • jcrist
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: applying new validation to the pairwise_distances metrics function.
Description check ✅ Passed The description is directly related to the changeset, explaining the conversion to check_array validation flow and the specific areas affected.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

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

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

@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.

🧹 Nitpick comments (1)
python/cuml/tests/test_metrics.py (1)

1525-1539: ⚡ Quick win

Add single-feature degenerate layout coverage.

This regression test currently validates only the X.shape == (1, n_features) degenerate path. The paired logic change also targets degenerate feature-dimension inputs (X.shape == (n_samples, 1)), so that branch should be covered too.

Proposed extension
 `@pytest.mark.parametrize`(
     "x_order,y_order",
     [("C", "C"), ("C", "F"), ("F", "C"), ("F", "F")],
 )
-def test_pairwise_distances_degenerate_x_layout(x_order, y_order):
+@pytest.mark.parametrize("x_shape", [(1, 4), (10, 1)])
+def test_pairwise_distances_degenerate_x_layout(x_order, y_order, x_shape):
     # When X has a degenerate shape (1 sample), it is both C- and
     # F-contiguous, so the implementation lets Y choose the layout.
     # Verify all four input layout combinations match sklearn.
     rng = np.random.RandomState(0)
-    X = np.asarray(rng.random_sample((1, 4)), order=x_order, dtype=np.float64)
-    Y = np.asarray(rng.random_sample((10, 4)), order=y_order, dtype=np.float64)
+    X = np.asarray(rng.random_sample(x_shape), order=x_order, dtype=np.float64)
+    Y = np.asarray(
+        rng.random_sample((10, x_shape[1])),
+        order=y_order,
+        dtype=np.float64,
+    )
     S = cp.asnumpy(pairwise_distances(X, Y, metric="euclidean"))
     S_ref = sklearn_pairwise_distances(X, Y, metric="euclidean")
     np.testing.assert_array_almost_equal(S, S_ref, decimal=12)
🤖 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 `@python/cuml/tests/test_metrics.py` around lines 1525 - 1539, Extend the
existing test_pairwise_distances_degenerate_x_layout to also cover the
degenerate-feature case where X has shape (n_samples, 1) (i.e., single feature)
so the branch that handles degenerate feature-dimension inputs is exercised;
duplicate the parametrized x_order,y_order combinations and create a second
subtest that constructs X with shape (10, 1) (or similar multi-sample
single-feature) and Y with compatible shape, call pairwise_distances(X, Y,
metric="euclidean") and compare its result to sklearn_pairwise_distances using
np.testing.assert_array_almost_equal (keep the same dtype/order handling and
decimal tolerance as the original test).
🤖 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.

Nitpick comments:
In `@python/cuml/tests/test_metrics.py`:
- Around line 1525-1539: Extend the existing
test_pairwise_distances_degenerate_x_layout to also cover the degenerate-feature
case where X has shape (n_samples, 1) (i.e., single feature) so the branch that
handles degenerate feature-dimension inputs is exercised; duplicate the
parametrized x_order,y_order combinations and create a second subtest that
constructs X with shape (10, 1) (or similar multi-sample single-feature) and Y
with compatible shape, call pairwise_distances(X, Y, metric="euclidean") and
compare its result to sklearn_pairwise_distances using
np.testing.assert_array_almost_equal (keep the same dtype/order handling and
decimal tolerance as the original test).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 4dc1e803-bb73-4339-ac96-5cf5854b54e4

📥 Commits

Reviewing files that changed from the base of the PR and between 9a8a7c7 and 2d66a02.

📒 Files selected for processing (2)
  • python/cuml/cuml/metrics/pairwise_distances.pyx
  • python/cuml/tests/test_metrics.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • python/cuml/cuml/metrics/pairwise_distances.pyx

@jcrist jcrist left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice work!

@jcrist

jcrist commented May 7, 2026

Copy link
Copy Markdown
Member

/merge

@rapids-bot
rapids-bot Bot merged commit 09d461e into NVIDIA:main May 7, 2026
93 checks passed
@csadorf
csadorf deleted the issue-7998-apply-new-validation-to-metrics-pairwise-distances branch May 7, 2026 17:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Cython / Python Cython or Python issue improvement Improvement / enhancement to an existing function non-breaking Non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants