Skip to content

Add hadamard rotation in smelling - #586

Merged
mikemccand merged 4 commits into
mikemccand:mainfrom
shubhamvishu:hadamard
Jul 10, 2026
Merged

Add hadamard rotation in smelling#586
mikemccand merged 4 commits into
mikemccand:mainfrom
shubhamvishu:hadamard

Conversation

@shubhamvishu

@shubhamvishu shubhamvishu commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Notes :

Cohere smelling

python3.11 src/python/smell_vectors.py \
    ~/luceneutil/data/cohere-v3-wikipedia-en-scattered-1024d.docs.first1M.vec \
    4096 \
    --label docs \
    --quiet \
    --rotate

 

Metric cohere-v3 docs (1024d)
isotropy_diag (raw → rotated) 0.79 → 0.99
degenerate dims (raw → rotated) 1 → ~4 (jitter)
isotropy_full 0.177 (invariant)
TwoNN intrinsic dim ~54 (invariant)
PCA-95% / curvature 450 / 8.3× moderately curved
Rotation verdict for OSQ Strong fit - random Hadamard is enough

 
Common pattern: rotation drives isotropy_diag to ≈1.0 and reshapes per-dim distributions toward Gaussian on both datasets, while leaving every distance/spectrum metric unchanged.

@shubhamvishu
shubhamvishu requested a review from mikemccand June 10, 2026 13:52
@github-actions

Copy link
Copy Markdown

This PR has not had activity in the past 2 weeks, labeling it as stale. If the PR is waiting for review, notify the dev@lucene.apache.org list. Thank you for your contribution!

@github-actions github-actions Bot added the Stale label Jun 25, 2026

@mikemccand mikemccand left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks @shubhamvishu -- let's try to get these PRs merged soon so people can separately test pre-conditioning non-isotropic vector corpora.

@@ -0,0 +1,236 @@
#!/usr/bin/env python3
"""Hadamard rotation — Python implementation matching the structure of
org.apache.lucene.util.quantization.HadamardRotation.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

This builds on the upstream Lucene PR right? (Not yet merged?).

Oh, not builds on, just refers to that PR, because this is pure Python (+ NumPy) impl, cool. So we can merge this before upstream PR and let us testing Hadamard separately/decoupled, yay!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Thats correct!

what OSQ wants.

This is a Python implementation of the same logic; it is NOT bit-exact with the
Java version because it uses NumPy's PRNG instead of Java's Random LCG. The

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I'm confused -- is the rotation matrix a deterministic function of int dim? Or is there some randomness in its creation? If so, does it write the resulting matrix somewhere?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The rotation or "hadamard" matrix (0s and 1s) is same. The seed is a deterministic based on the dim which is used for RNG (permutation and negation). We don't need to store the matrix anywhere since a hadamard matrix of dim 2 looks like [[1,1] [1,-1]] and the same could be generated for any dim of power 2 (so its fixed not random).

@github-actions github-actions Bot removed the Stale label Jun 30, 2026
@shubhamvishu

Copy link
Copy Markdown
Collaborator Author

Thanks for taking a look @mikemccand. I fixed the conflicts now. This PR build on the other smelling PR, so maybe we could get that 1st or maybe we could merge this one (has the union of code changes).

# Conflicts:
#	src/python/smell_vectors.py
@shubhamvishu
shubhamvishu requested a review from mikemccand July 9, 2026 21:08

@mikemccand mikemccand left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I left minor / non-blocking comments -- let's merge and address comments in follow-on -- thank you @shubhamvishu! Isotropic vectors here we go!

ap.add_argument(
"--rotate",
action="store_true",
help="apply Lucene-faithful Hadamard rotation to every sampled vector in memory before analysis (no rotated .vec file is written)",

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Looks like it's only for the ID ("intrinsic dimensions") smelling?

print("Re-running smell with Hadamard rotation applied in memory (Lucene-faithful)...")
print("=" * 104)
print()
ROTATE = True

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Kinda messy -- would be better to pass a parameter?


def _maybe_rotate(samples, dim):
"""If ROTATE is on, return a Hadamard-rotated copy of `samples` (rows are rotated independently)."""
if not ROTATE:

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Better to just not call this method if not ROTATE from above?

@mikemccand
mikemccand merged commit 38be4b4 into mikemccand:main Jul 10, 2026
1 check passed
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.

2 participants