Skip to main content

NumPy-first similarity scores for strings, vectors, points, and sets.

Project description

simmetry

Similarity scores for strings, vectors, points, and sets with a small, NumPy-first API.

PyPI · GitHub · Docs · Changelog

Install

pip install simmetry
pip install "simmetry[fast]"      # Numba acceleration for euclidean_sim / manhattan_sim
pip install "simmetry[ann-hnsw]"  # hnswlib ANN backend
pip install "simmetry[ann-faiss]" # FAISS ANN backend
pip install "simmetry[ann]"       # both ANN backends

Project Status

  • Current version: 1.1.0
  • Maturity: Beta (API stabilising; pin to minor versions in production)
  • Versioning: semantic versioning; breaking changes bump the minor version until 2.0

Quickstart

One function

from simmetry import similarity

similarity("kitten", "sitting", metric="levenshtein")
similarity([1, 2, 3], [1, 2, 4], metric="cosine")
similarity((41.1, 29.0), (41.2, 29.1), metric="haversine_sim")  # returns [0, 1]
similarity({1, 2, 3}, {2, 3, 4}, metric="jaccard")

Pairwise matrices (vectors)

import numpy as np
from simmetry import pairwise

X = np.random.randn(1000, 128)
S = pairwise(X, metric="cosine")          # (1000, 1000)
D = pairwise(X, metric="cosine_distance") # 1 - cosine, sklearn-compatible

Top-k search (exact)

import numpy as np
from simmetry import topk

X = np.random.randn(5000, 64)
q = np.random.randn(64)
idx, scores = topk(q, X, k=10, metric="cosine")
# idx and scores are sorted descending (highest similarity first)

Available Metrics

from simmetry import available

available()           # all registered metrics
available("vector")
available("string")
available("point")
available("set")

Vectors

Metric Returns
cosine [-1, 1]
cosine_distance [0, 2] · 1 - cosine
dot unbounded inner product
euclidean_sim (0, 1] · 1 / (1 + dist)
manhattan_sim (0, 1] · 1 / (1 + dist)
pearson [-1, 1]
hamming [0, 1] · normalized for equal-length sequences

Strings

Metric Notes
levenshtein normalized edit distance
jaro_winkler prefix-weighted character matching
ngram_jaccard character trigram Jaccard (default n=3)
token_jaccard whitespace-token Jaccard

Points / Geo

Metric Returns
euclidean_2d (0, 1] · 2D Cartesian similarity
haversine_sim [0, 1] · geographic similarity (antipodal ≈ 0)

haversine_km is available as a utility function (not a registered metric) that returns raw kilometers:

from simmetry.points import haversine_km

km = haversine_km((40.7, -74.0), (51.5, -0.1))  # ~5 570 km

Sets

Metric Formula
jaccard |A∩B| / |A∪B|
dice 2|A∩B| / (|A| + |B|)
overlap |A∩B| / min(|A|, |B|)
tversky |A∩B| / (|A∩B| + α|A\B| + β|B\A|)

tversky with alpha=beta=1 equals Jaccard; alpha=beta=0.5 equals Dice. Call it directly with custom weights: tversky(A, B, alpha=0.3, beta=0.7).

Auto Metric Selection

Auto mode applies fixed type-based rules — no learning, no randomness.

from simmetry import infer_metric, similarity

infer_metric("samplecorp", "sample corp")   # "jaro_winkler"
infer_metric((41.0, 29.0), (41.1, 29.1))   # "haversine_sim"
infer_metric({1, 2, 3}, {2, 3, 4})         # "jaccard"

similarity("samplecorp", "sample corp")     # uses inferred metric

Selection order:

  1. list[str] / tuple[str] (including empty) → jaro_winkler
  2. str + strjaro_winkler
  3. tuple of 2 numbers with valid lat/lon range ([-90, 90] × [-180, 180]) → haversine_sim
  4. set / frozensetjaccard
  5. numeric vectors → cosine
  6. fallback → cosine

Note: Only tuple inputs (not list) trigger the geo heuristic to avoid ambiguity with 2D numeric vectors. [1.0, 2.0] routes to cosine; (1.0, 2.0) routes to haversine_sim. Pass metric="haversine_sim" explicitly when in doubt.

Batch String APIs

from simmetry.strings import pairwise_strings, topk_strings

S = pairwise_strings(
    ["item_one", "item_two"],
    ["item_one", "item_alt"],
    metric="jaro_winkler",
)
idx, scores = topk_strings(
    "samplecorp",
    ["samplecorp", "examplefinance", "testgroup"],
    k=2,
    metric="levenshtein",
)

Batch Point APIs (Geo / 2D)

from simmetry.points import pairwise_points, topk_points

pts = [(41.0, 29.0), (41.01, 29.01), (40.9, 28.9)]
S = pairwise_points(pts, metric="haversine_sim")              # similarity matrix
idx, scores = topk_points((41.0, 29.0), pts, k=2, metric="haversine_sim")

haversine_km is also accepted by pairwise_points and topk_points for raw-distance use cases. topk_points(..., metric="haversine_km") ranks by ascending distance (nearest first).

ANN Top-k (Optional)

For large vector corpora (100k+), use approximate nearest neighbour backends.

hnswlib

import numpy as np
from simmetry.ann import build_hnsw

X = np.random.randn(200_000, 128).astype("float32")
X /= np.linalg.norm(X, axis=1, keepdims=True)

index = build_hnsw(X, space="cosine")
labels, distances = index.query(X[0], k=10)

faiss

import numpy as np
from simmetry.ann import build_faiss

X = np.random.randn(200_000, 128).astype("float32")
X /= np.linalg.norm(X, axis=1, keepdims=True)

index = build_faiss(X, metric="ip")
labels, scores = index.query(X[0], k=10)

SimIndex (Exact or ANN)

SimIndex provides a unified interface across all backends. query() always returns (indices, similarities) — ANN distances are converted to similarities internally so results are directly comparable across backends.

import numpy as np
from simmetry import SimIndex

X = np.random.randn(50_000, 128).astype("float32")
index = SimIndex(metric="cosine", backend="exact").add(X)
idx, scores = index.query(X[0], k=10)
# scores are cosine similarities, sorted descending

Switch backends without changing the query code:

index_hnsw  = SimIndex(metric="cosine", backend="hnsw").add(X)
index_faiss = SimIndex(metric="cosine", backend="faiss").add(X)

Composite Records

Weight multiple fields with different metrics:

from simmetry import similarity

a = {"name": "Entity One", "city": "CityAlpha", "loc": (41.0, 29.0)}
b = {"name": "Entity One Extended", "city": "CityAlpha", "loc": (41.01, 28.99)}

score = similarity(
    a,
    b,
    metric={"name": "jaro_winkler", "loc": "haversine_sim"},
    weights={"name": 0.7, "loc": 0.3},
)

Fields missing from either record raise KeyError with a descriptive message.

Custom Metrics

from simmetry import register, similarity

def my_metric(a, b):
    return 1.0 if a == b else 0.0

register("exact_match", my_metric, kind="generic")
similarity("foo", "foo", metric="exact_match")  # 1.0

Benchmarks

The project includes a benchmark harness in bench/run.py. Comparative benchmarks against rapidfuzz, scikit-learn, and ANN libraries are not yet published.

python bench/run.py

Scope and Roadmap

Planned additions:

  • String metrics: Hamming (string variant), BM25-style text ranking helpers
  • Published comparative benchmarks (RapidFuzz / sklearn / faiss baselines)
  • Hosted docs site

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

simmetry-1.1.0.tar.gz (23.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

simmetry-1.1.0-py3-none-any.whl (21.7 kB view details)

Uploaded Python 3

File details

Details for the file simmetry-1.1.0.tar.gz.

File metadata

  • Download URL: simmetry-1.1.0.tar.gz
  • Upload date:
  • Size: 23.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for simmetry-1.1.0.tar.gz
Algorithm Hash digest
SHA256 24fb49abe795b30123dee26d4a553c9ea316b35449bd4311dcdaf2389afb69a2
MD5 57ea4a7dedd862ca3a36b69f8730bb97
BLAKE2b-256 5f02b6efc7037dae651fe6b9e2bd58e640c0cd250828f60cb7401d9bb4726cc4

See more details on using hashes here.

File details

Details for the file simmetry-1.1.0-py3-none-any.whl.

File metadata

  • Download URL: simmetry-1.1.0-py3-none-any.whl
  • Upload date:
  • Size: 21.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for simmetry-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e1fa382c940e5c2bd2a908aa62bd4e2b73b0027d87218a33804ee211df9f52e5
MD5 b78071ba843cdf6a216d4a871596b5b6
BLAKE2b-256 d891de28750152316ae6e273bad93b93121990237fa491d01ac0b390c919f4c4

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page