Select a small, maximally diverse subset from a large set of protein sequences using geometric sketching (Hie et al. 2019, Cell Systems 8, 483–493).
Two embedding modes are supported:
- K-mer mode — fast, self-contained C pipeline: read a FASTA file, compute k-mer count-sketch embeddings, and sketch.
- Matrix mode — bring your own embeddings (e.g. from a protein
language model): provide a
.npymatrix and an IDs file.
In both cases the output is a text file of selected sequence IDs, one per line, in the order they appeared in the input.
# Build (requires a C11 compiler — gcc, clang, etc.)
cd c/
make
# Select 100 diverse sequences from a FASTA
./bin/geosketch --fasta seqs.fa --M 100 --out sketch.txt
# Check the result
wc -l sketch.txt # 100
head sketch.txt # sequence IDscd c/
make # produces bin/geosketch and bin/clustersketch
make clustersketch # build only the clustering baseline tool
make clean # remove binariesclustersketch links against easel (hmmer-3.4/easel), so ensure HMMER
is built and libeasel.a exists. You can override EASEL_DIR if needed:
cd c/
make clustersketch EASEL_DIR=/path/to/hmmer-3.4/easelThe geosketch binary has no external dependencies beyond a standard C11
compiler and the math library (-lm), both of which are available on any
Linux / macOS system.
On the FASRC cluster the default gcc module works out of the box:
module load gcc # if not already loaded
cd c/ && make./bin/geosketch \
--fasta input.fa \
--M 100 \
--out sketch.txt \
--seed 0This reads input.fa, computes a k-mer count-sketch embedding for each
sequence (default: 4-mers hashed into 512 dimensions), runs geometric
sketching to select 100 diverse sequences, and writes their IDs to
sketch.txt.
./bin/geosketch \
--matrix embeddings.npy \
--ids ids.txt \
--M 100 \
--out sketch.txt \
--seed 0embeddings.npy is a 2D NumPy array of shape (N, D) with dtype
float32 or float64. ids.txt has one sequence ID per line (same
order as the matrix rows). This mode is intended for protein language
model embeddings produced by an external Python script.
By default, when a grid cell is sampled, a random point within that cell is chosen. An optional scores file changes this to pick the highest-scoring (or lowest-scoring) point instead:
./bin/geosketch \
--fasta input.fa \
--M 100 \
--out sketch.txt \
--scores bitscores.txt \
--score-policy maxbitscores.txt has one floating-point value per line, in the same order
as the FASTA sequences.
Usage:
geosketch --fasta <path> --M <int> --out <path> [options]
geosketch --matrix <path> --ids <path> --M <int> --out <path> [options]
Modes:
--fasta <path> Input FASTA file (k-mer embedding mode)
--matrix <path> Precomputed embedding matrix (.npy, float32/64)
--ids <path> Sequence IDs file (required with --matrix)
Required:
--M <int> Sketch size (number of sequences to select)
--out <path> Output file (selected IDs, one per line)
K-mer options (--fasta mode only):
--kmer-k <int> K-mer length [default: 4]
--dim <int> Embedding dimension (hash buckets) [default: 512]
Geosketch options:
--seed <uint64> Random seed [default: 0]
--alpha <float> Binary-search tolerance [default: 0.1]
--max-iter <int> Max binary-search iterations [default: 200]
--k auto|<int> Target covering-box count [default: auto = M]
Score-based within-cell selection:
--scores <path> Scores file (one float per line)
--score-policy <p> uniform | max | min | max_ties | min_ties
[default: uniform]
Other:
--verbose <int> Verbosity level (0 = silent) [default: 0]
--help Show help message
./bin/geosketch --fasta proteins.fa --M 200 --out sketch200.txt --seed 42# Same seed → identical output
./bin/geosketch --fasta proteins.fa --M 100 --out a.txt --seed 0
./bin/geosketch --fasta proteins.fa --M 100 --out b.txt --seed 0
diff a.txt b.txt # no differences# 3-mers into 256 dimensions (faster, lower resolution)
./bin/geosketch --fasta proteins.fa --M 100 --out sketch.txt \
--kmer-k 3 --dim 256
# 5-mers into 1024 dimensions (higher resolution, more memory)
./bin/geosketch --fasta proteins.fa --M 100 --out sketch.txt \
--kmer-k 5 --dim 1024# Pick the highest-bitscore sequence from each grid cell
./bin/geosketch --fasta hits.fa --M 100 --out sketch.txt \
--scores bitscores.txt --score-policy max
# Break ties randomly among equally-scoring sequences
./bin/geosketch --fasta hits.fa --M 100 --out sketch.txt \
--scores bitscores.txt --score-policy max_ties --seed 7# Python side: save embeddings + IDs
# python save_plm_embeddings.py --fasta proteins.fa \
# --out-matrix embeddings.npy --out-ids ids.txt
# C side: sketch from the precomputed matrix
./bin/geosketch --matrix embeddings.npy --ids ids.txt \
--M 100 --out sketch.txt# Force 30 boxes but select 100 sequences.
# First round: 1 sample per box (30). Overflow rounds fill the
# remaining 70 by prioritising cells with fewer sequences left.
./bin/geosketch --fasta proteins.fa --M 100 --k 30 --out sketch.txt./bin/geosketch --fasta proteins.fa --M 100 --out sketch.txt \
--verbose 2 2>geosketch.log
# geosketch.log contains binary-search iterations and cell counts-
Embed — each protein sequence is mapped to a D-dimensional vector. In FASTA mode this is a CountSketch of k-mer frequencies, L2-normalised so sequence length does not dominate the geometry.
-
Translate & scale — the embedding matrix is shifted so all values are non-negative, then divided by the global maximum so everything lies in [0, 1].
-
Plaid covering — a binary search finds a box side length
unitsuch that the number of non-empty axis-aligned grid cells is approximately equal to the targetk(default: M). -
Sample — one cell is drawn uniformly at random, then one point is drawn from that cell (uniformly, or by score). The cell is removed from the round; once all cells have been visited, a new round begins.
-
Overflow — if M exceeds the number of cells, additional samples are drawn by sorting the remaining non-empty cells by size (ascending) and sampling one from each, smallest first. This maximises diversity by avoiding over-sampling from large cells.
Peak memory is roughly N × D × 24 bytes (the embedding matrix in
double, plus argsort indices and the grid table in int32).
| N (sequences) | D | Approx. peak |
|---|---|---|
| 100 K | 512 | ~1.2 GB |
| 1 M | 512 | ~12 GB |
| 1 M | 256 | ~6 GB |
| 1 M | 1024 | ~24 GB |
These numbers are comfortable on HPC nodes. To reduce memory, lower D or use a high-memory node.
Scripts under scripts/ generate baseline sketch lists from a jackhmmer run
(run dir must contain hits/hits.fa, hits/hits_scores.txt, hits/hits.sto).
Generate list files (four methods: lowest E-value, random, stratified E-random,
stratified E-minE) then build .fa, .sto, .hmm, and *_alistat.txt per list
using the shared build step below.
Submit as one SLURM job (recommended):
cd /path/to/Sketching
mkdir -p logs
# Optional: use a different run dir
export RUN=experiments/globins
sbatch scripts/run_baseline_pipeline.shThe job runs from Sketching/; default run dir is experiments/globins. Logs go to
logs/run_baseline.out and logs/run_baseline.err. This only generates sketch list
files (no build, no tables).
Or run the same steps by hand (no sbatch):
# From Sketching/; set RUN for your run directory (can use a different run later)
RUN=experiments/globins
for M in 1000 500 100 50; do
./scripts/low_compute_baseline.sh "$RUN" $M
done
# Build all sketch dirs and write tables
./scripts/run_build_and_tables.shMethod 1 writes one list file per M. Methods 2–4 write 100 list files per M (different seeds; stratifiedEminE breaks ties by seed).
Cluster hits/hits.sto by percent identity using easel’s
esl_msacluster_SingleLinkage() and sample sequences to size M with
geosketch-style overflow sampling.
SLURM job (recommended):
cd /path/to/Sketching
mkdir -p logs
RUN=experiments/globins sbatch scripts/run_cluster_pipeline.sh 1000 500 100 50This writes:
- Random replicates:
experiments/<exp>/msacluster_<M>/sketch1.txt ... sketch100.txt - minE replicates:
experiments/<exp>/msacluster_minE_<M>/sketch1.txt ... sketch100.txt
To change score sampling policy or parameters:
RUN=experiments/<exp> sbatch scripts/run_cluster_pipeline.sh 100 \
--score-policy uniform --replicates 100 --alpha 0.1 --max-iter 200cd /path/to/Sketching
mkdir -p logs
# 1) Search (put query.fa in experiments/<exp>/ first)
sbatch scripts/search.sh experiments/<exp>
# 2) Baseline list generation
RUN=experiments/<exp> sbatch scripts/run_baseline_pipeline.sh
# 3) Geosketch list generation (array job)
RUN=experiments/<exp> sbatch scripts/run_geosketch_pipeline.sh
# 4) PLM geosketch list generation (array job)
# Requires merged model NPZ files in hits/ESM2_<size>_<dim>.npz.
# If part shards exist (hits/ESM2_<size>_<dim>.part-*.npz), they are merged first.
# Length sorting is enabled by default; this creates/reuses:
# experiments/<exp>/hits/hits_scores_sorted.txt
RUN=experiments/<exp> sbatch scripts/run_plm_geosketch_pipeline.sh
# 5) High-compute clustering baseline (optional)
RUN=experiments/<exp> sbatch scripts/run_cluster_pipeline.sh 1000 500 100 50
# 6) Build all sketches + write tables + merge timing
RUN=experiments/<exp> sbatch scripts/run_build_and_tables.sh
# 7) Plots
python3 scripts/plot_baseline_results.py --run-dir experiments/<exp>Outputs are organized under experiments/<exp>/ (hits/, tables/, plots/, times/).
Use the orchestrator when you want a single command to submit the full workflow and wait for successful completion of the final build/tables step.
cd /path/to/Sketching
mkdir -p logs
# Uses k=3, dims 64/128/256/512/1024 for kmer geosketch
# Uses merged PLM NPZs (hits/ESM2_<size>_<dim>.npz); merged from part shards when needed
RUN=experiments/<exp> M_VALS="1000 500 100 50" REPLICATES=100 \
./scripts/run_full_pipeline.sh
# Submit only (do not block/wait):
RUN=experiments/<exp> M_VALS="1000 500 100 50" REPLICATES=100 \
./scripts/run_full_pipeline.sh --no-waitscripts/plm_embed.py is a config-free, high-throughput embedding writer adapted
from PSALM-2/src/sketch_embed.py and intended for ESM2 HF IDs only.
Supported model IDs:
facebook/esm2_t6_8M_UR50D(320)facebook/esm2_t12_35M_UR50D(480)facebook/esm2_t30_150M_UR50D(640)facebook/esm2_t33_650M_UR50D(1280)
Example:
source activate /n/eddy_lab/Lab/protein_annotation_dl/PSALM-2/psalm2
python3 scripts/plm_embed.py \
--fasta experiments/<exp>/hits/hits.fa \
--output experiments/<exp>/hits/ESM2_650M_1280 \
--model facebook/esm2_t33_650M_UR50D \
--scores experiments/<exp>/hits/hits_scores.txt \
--sorted-scores-out experiments/<exp>/hits/hits_scores_sorted.txtBy default, embeddings are length-sorted for throughput. When sorting is enabled
and --scores is provided, hits_scores_sorted.txt is created/reused so minE
selection stays aligned with embedding ID order.
experiments/<exp>/
├── hits/
│ ├── hits.fa
│ ├── hits.sto
│ ├── hits.hmm
│ ├── hits_alistat.txt
│ ├── hits_scores.txt
│ ├── hits_scores_sorted.txt # created/reused when PLM embedding sorts by length
│ ├── hits.domtblout
│ ├── hits_clan.txt # seq_id<TAB>family clan assignments
│ ├── hits_fe.txt # family counts + X + entropy (bits)
│ ├── hits_fe_bins.txt # fixed FE categories used by all sketch FE files
│ ├── <model>_<size>_<dim>.part-*.npz # raw embedding shards from plm_embed.py
│ └── <model>_<size>_<dim>.npz # merged embedding per model for sketching
├── minE_<M>/ ... # includes sketch*.txt and sketch*_fe.txt
├── randomsample_<M>/ ... # includes sketch*.txt and sketch*_fe.txt
├── stratifiedErandom_<M>/ ... # includes sketch*.txt and sketch*_fe.txt
├── stratifiedEminE_<M>/ ... # includes sketch*.txt and sketch*_fe.txt
├── geosketch_kmer_<k>_<dim>_<M>/ ... # includes sketch*.txt and sketch*_fe.txt
├── geosketch_kmer_<k>_<dim>_minE_<M>/ ...
├── geosketch_plm_<size>_<dim>_<M>/ ...
├── geosketch_plm_<size>_<dim>_minE_<M>/ ...
├── plots/
├── tables/
└── times/
- Hie, B., Cho, H., DeMeo, B., Bryson, B. & Berger, B. Geometric Sketching Compactly Summarizes the Single-Cell Transcriptomic Landscape. Cell Systems 8, 483–493 (2019). https://doi.org/10.1016/j.cels.2019.05.003