Skip to content

Parallelize clustering distance calculation - #279

Open
tmsincomb wants to merge 4 commits into
mainfrom
agent/278-add-multiprocessing-or-multithreading
Open

Parallelize clustering distance calculation#279
tmsincomb wants to merge 4 commits into
mainfrom
agent/278-add-multiprocessing-or-multithreading

Conversation

@tmsincomb

@tmsincomb tmsincomb commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • replace Python-level pairwise Levenshtein calls with RapidFuzz's native threaded distance matrices for every clustering mode, including pad_somatic
  • calculate somatic padding from an inverted mutation-to-row index while preserving per-chain intersections, duplicate handling, V-gene filtering, and distance clamping
  • preserve linked/unlinked and grouped/ungrouped output values, ordering, reconstruction, metadata, and input side effects

Performance

  • linked 219-row distance matrix: 29.4x faster without somatic padding
  • linked 219-row distance matrix: 81.6x faster with somatic padding
  • linked 219-row distance matrix: 86.2x faster with somatic padding and V-gene-only filtering
  • multiprocessing was benchmarked but omitted because process startup, IPC, and worker copies made it substantially slower than the native-threaded/vectorized path

Validation

  • 6 focused clustering tests
  • 174 unit tests
  • 3 integration tests
  • exact merge-base comparison across 32 option combinations, including distance matrices, outputs, labels, types, metadata, reconstruction, and input side effects
  • Pyright, pre-commit, Poetry lock validation, and CodeRabbit (0 issues)

Closes #278

Add deterministic Hypothesis differential coverage, bounded exhaustive cases, and threaded boundary checks. Preserve the legacy kernel for unsupported values so optimized clustering keeps exact behavior.
@tmsincomb tmsincomb self-assigned this Aug 5, 2026
@tmsincomb
tmsincomb marked this pull request as ready for review August 5, 2026 18:14
@tmsincomb
tmsincomb requested a lite review from Copilot August 5, 2026 18:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR accelerates clustering by replacing Python-level pairwise Levenshtein computations with RapidFuzz’s native (optionally threaded) distance matrix computation, while preserving legacy behavior—including somatic padding semantics and output equivalence.

Changes:

  • Reworks Cluster._get_distance_df to use RapidFuzz cdist (threaded above a row-count threshold) with a guarded legacy fallback for edge/malformed inputs.
  • Implements somatic-padding subtraction via an inverted mutation→row index and clamps distances to preserve legacy non-negativity behavior.
  • Expands unit coverage with focused regression tests and a large differential/property-based suite; adds greedy marker and Hypothesis dependency/ignores.

Reviewed changes

Copilot reviewed 5 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/sadie/cluster/cluster.py Adds RapidFuzz-based fast path for distance matrices with somatic-padding support and a legacy fallback.
tests/unit/cluster/test_cluster.py Adds targeted regression tests for threaded distance behavior and linked somatic padding/output preservation.
tests/unit/cluster/test_cluster_differential.py Adds extensive differential/property-based equivalence testing vs the legacy kernel.
pytest.ini Adds greedy marker and ignores .hypothesis artifacts.
pyproject.toml Adds RapidFuzz runtime dependency and Hypothesis dev dependency.
poetry.lock Locks added Hypothesis (and transitive) dependencies.
.gitignore Ignores .hypothesis/ directory.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +152 to +171
stable_types = (str, bytes, bool, int, float, complex, type(None))
use_fast_path = len(df) > 0 and all(
type(index) in stable_types or index is pd.NA or index is pd.NaT for index in df.index
)
if self.pad_somatic and len(self.pad_somatic_values) not in (1, 2):
use_fast_path = False
if use_fast_path:
use_fast_path = all(
type(row[metric]) in stable_types or row[metric] is pd.NA or row[metric] is pd.NaT
for row in df_lookup.values()
for metric in self.lookup
)
if use_fast_path and self.pad_somatic:
use_fast_path = all(
type(mutations) in (list, tuple, np.ndarray)
and not (type(mutations) is np.ndarray and mutations.ndim != 1)
and all(type(mutation) in (str, np.str_) and "\0" not in mutation for mutation in mutations)
for row in df_lookup.values()
for mutations in (row[metric] for metric in self.pad_somatic_values)
)
Comment thread src/sadie/cluster/cluster.py Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
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.

Add multiprocessing or multithreading to clustering

2 participants