Skip to content

Add pdist compatible python function signature - #1

Merged
candytaco merged 3 commits into
masterfrom
claude/sleepy-goodall-p57x3w
Sep 15, 2026
Merged

candytaco merged 3 commits into
masterfrom
claude/sleepy-goodall-p57x3w

Conversation

@candytaco

Copy link
Copy Markdown
Collaborator

Make API fully compatible with scipy's pdist, and squash memory leaks

…ture

- pdist(X, metric='euclidean', *, out=None) dispatches to the existing distance functions by metric name
- allocates the condensed distance matrix when out is None and validates shape, dtype and contiguity otherwise
- document the new entry point in the readme

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wqz9cM21KFZ7LP6MfZd9o
- decrement references to arrays produced by PyArray_FROM_OTF and PyArray_FROM_O on every return path
- replace the explicit destructor call on the demeaned correlation array with Py_DECREF

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wqz9cM21KFZ7LP6MfZd9o
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wqz9cM21KFZ7LP6MfZd9o
Copilot AI lite review requested due to automatic review settings September 15, 2026 20:11
@candytaco
candytaco merged commit 6c3e060 into master Sep 15, 2026

Copilot AI 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.

🟡 Changes recommended

Unresolved critical memory-safety and exception-handling issues block approval.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds a SciPy-compatible Pairwise.pdist API for Euclidean and correlation distances, with optional output arrays and improved cleanup.

Changes:

  • Adds and registers pdist(X, metric, *, out).
  • Documents the new API and compatibility.
  • Improves NumPy reference cleanup and validation.
File summaries
File Review summary
Readme.md Documents pdist; the RandomForest example conflicts with the stated compatibility (nit, 1 vote).
PairwiseDistances.h Declares and registers the new pdist entry point.
PairwiseDistances.cpp Contains unresolved zero-row memory corruption (critical, 3 votes), unchecked conversion failures that can crash (critical, 2 votes), incorrect exception returns (moderate, 2 votes), leaked indexers in multiple paths (moderate, 3 votes each), and signed-char tolower undefined behavior (moderate, 1 vote).
Review details

Suppressed comments (5)

PairwiseDistances.cpp:535

  • PyArray_FROM_O can fail and return nullptr (for example when conversion or __array__ raises), but PyArray_TYPE(probe) dereferences it immediately. Check probe before reading its type and return nullptr so invalid inputs raise their original Python exception instead of crashing the interpreter.
	PyArrayObject* probe = (PyArrayObject*)PyArray_FROM_O(arg1);
	int arrayType = PyArray_TYPE(probe);
	Py_DECREF(probe);

PairwiseDistances.cpp:555

  • This branch sets a Python exception and then returns None. CPython treats a non-NULL return with an active exception as a SystemError, so invalid cluster types do not reach callers as the intended ValueError; return nullptr after setting the error.
		Py_XDECREF(clusterDistances);
		PyErr_SetString(PyExc_ValueError, "Cluster solutions need to be in uint8 or uint16");
		Py_RETURN_NONE;

PairwiseDistances.cpp:515

  • The new NumPy decrefs do not release the itemIndexer and solutionIndexer allocated at lines 487-488. Each successful AVX call therefore still leaks both lookup tables; delete both indexers before returning.
	Py_DECREF(clusterSolutions);
	Py_DECREF(clusterDistances);

PairwiseDistances.cpp:231

  • tolower requires either EOF or an unsigned char value, but metric[i] may be a negative char for non-ASCII metric strings. An unsupported UTF-8 metric can therefore invoke undefined behavior before the intended ValueError; cast through unsigned char before calling tolower.
		metric[i] = (char)tolower(metric[i]);

Readme.md:22

  • This newly added example includes RandomForest, but the new pdist entry point only accepts euclidean and correlation; the following statement that out contains the same data as pdist is therefore false for the listed random-forest function. Qualify that statement or separate the random-forest legacy API from the pdist-compatible functions.
Pairwise.GetPairwise[Euclidean|Correlation|RandomForest]Distance(data, out)
  • Files reviewed: 3/3 changed files
  • Comments generated: 5
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread PairwiseDistances.cpp
}
npy_intp m = PyArray_DIM(XArray, 0);
Py_DECREF(XArray);
npy_intp numCondensed = m * (m - 1) / 2;
Comment thread PairwiseDistances.cpp
Comment on lines +454 to +456
PyArrayObject* probe = (PyArrayObject*)PyArray_FROM_O(arg1);
int arrayType = PyArray_TYPE(probe);
Py_DECREF(probe);
Comment thread PairwiseDistances.cpp
Comment on lines +434 to +435
Py_DECREF(clusterSolutions);
Py_DECREF(clusterDistances);
Comment thread PairwiseDistances.cpp
Comment on lines +474 to 476
Py_XDECREF(clusterDistances);
PyErr_SetString(PyExc_ValueError, "Cluster solutions need to be in uint8 or uint16");
Py_RETURN_NONE;
Comment thread PairwiseDistances.cpp
Comment on lines +594 to +595
Py_DECREF(clusterSolutions);
Py_DECREF(clusterDistances);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants