When to use this
Use this recipe when you are running a model-independent search for new physics. You know the Standard Model well — you have large background samples — but you do not know what the BSM signal looks like. Every model-specific search you run is a hypothesis you had to propose in advance; this recipe finds the hypotheses you did not think of.
The approach: embed every event in the scattering feature space, build an HNSW vector index over the Standard Model background manifold using Sliced-Wasserstein distance as the metric, and flag events that are far from any known SM event. No signal model, no retraining, no per-hypothesis tuning. The threshold is calibrated from a held-out background sample so the false-positive rate is controlled.
Applicable to any event-level observable that can be converted to a scattering feature vector: jet substructure, MET + lepton topologies, photon clusters, displaced vertices. The same recipe runs at 1-D (time series), 2-D (detector images), and 3-D (full event volumetrics) without modification.
What it needs
Any event-level data that can be converted to scattering features. ROOT TTrees, HDF5 arrays, or pre-extracted feature vectors are all accepted. The background sample must be significantly larger than the signal sample (recommend ≥ 10× ratio).
# Option A: ROOT TTree
events.root
└── tree "Events"
├── jet_image float[64][64] # or constituent four-vectors
├── FullEventWeight float
└── (no isSignal branch needed — background only for index build)
# Option B: pre-extracted HDF5
background_features.h5
└── "features" float32 (N_bkg, D) # D-dimensional feature vectors
search_features.h5
└── "features" float32 (N_search, D)
# Option C: numpy arrays
background.npy # shape (N_bkg, D)
search.npy # shape (N_search, D)How it works
MCP tool call sequence
vikshep/ingest
vikshep/compute_scattering + vikshep/reduce_scattering
method="log_mean" rather than ratio, which preserves more information about the overall energy scale (since anomalies may be unusual in absolute amplitude, not just shape). The resulting feature space is the metric space in which distances are computed.vikshep/build_hnsw_index
vikshep/detect_anomaly
--threshold-quantile 0.99).(export)
What you get
- →anomalies.h5 — ranked event list with per-event SW₁ distance and anomaly score
- →distances.npy — raw distance array for all query events, shape (N_search,)
- →calibration.json — { threshold_tau, fpr_curve, n_background, n_flagged }
- →index.hnsw — persistent HNSW index; reload for future searches at zero recompute cost
- →feature_oids.txt — SHA3-256 OIDs of background and query feature tensors
Configure it
| param | default | description |
|---|---|---|
| dim | 2 | Input dimensionality (change to 1 or 3 for other data types) |
| group | so2 | Symmetry group for the scattering transform |
| J | 4 | Number of scales |
| Q | 1 | Voices per octave |
| L | 8 | Orientations. Fewer orientations → faster index build |
| reduction | log_mean | Feature reduction method. log_mean preserves absolute scale information |
| metric | sliced_wasserstein | Distance metric for the HNSW index |
| hnsw_M | 16 | HNSW connectivity parameter. Higher M → better recall, larger index |
| hnsw_ef | 200 | HNSW construction-time search factor. Higher → slower build, better index quality |
| threshold_quantile | 0.99 | FPR target: flag events above this quantile of background distances |
| k_neighbors | 5 | k-NN lookups per query event during anomaly scoring |
Run it — CLI
bun run main.ts process \
--input events.root \
--recipe bsm-anomaly \
--threshold-quantile 0.99 \
--background-input background.root \
--hnsw-M 16 \
--output results/bsm-anomaly/Run it — Dashboard
⬜
Dashboard screenshot — coming in Pilot
The dashboard shows the 2-D UMAP projection of the scattering embedding — background events as a dense cloud, flagged events as highlighted outliers. The threshold ring is draggable and the flagged set updates live. Available in the Pilot release.