Skip to content
St4NNiPublic

About

A fast non-cryptographic hash for packed canonical DNA k-mers

Resources

Stars

4 stars

Watchers

1 watching

Forks

Repository files navigation

Documentation License

jamhash

jamhash provides a fast, non-cryptographic 64-bit hash for canonical DNA kmers packed into a u64 with two bits per base. jam-rs uses this mapping in its reference-guided FracMinHash screening pipeline; this repository evaluates the hash mapping, not the accuracy or safety of that complete system.

cargo add jamhash

The minimum supported Rust version (MSRV) is 1.86.

use jamhash::jamhash_u64;

// Canonicalization and two-bit packing are caller responsibilities.
let canonical_kmer = 0x0000_006f_93b4_b1bc;
assert_eq!(jamhash_u64(canonical_kmer), 0xaf47_a961_8481_4793);

The exact jamhash_u64 formula and compatibility rules are documented in ALGORITHM.md. A u64 can hold at most 32 two-bit bases. The checked-in exhaustive evidence is specifically for k=21.

jamhash_u64(0) == 0 is intentional. Callers must define a zero policy; current jam-rs screening paths exclude zero before applying the FracMinHash threshold. The archived analysis counted zero inclusively; both conventions are reported below.

Benchmarks

Criterion results at source revision 6c385660e1c115d015eb06a8bfa3aee73a452eb8 on an AMD Ryzen 9 7945HX used rustc 1.97.1 (LLVM 22.1.6), x86_64-unknown-linux-gnu, opt-level=3, LTO, and one codegen unit:

Operation Median 95% CI Throughput
jamhash_u64 2.722 ns 2.715–2.728 ns 367.37 million/s
XXH3 xxh3_64, pre-encoded big-endian u64 5.811 ns 5.807–5.824 ns 172.07 million/s
fastmurmur3 murmur3_x64_128, low 64 bits, pre-encoded, seed 42 10.575 ns 10.523–10.609 ns 94.56 million/s

All three functions received the same 14 fixed values. Input selection and byte encoding were outside the measured operation, and inputs and outputs passed through black_box. These are microbenchmark results for the named machine and build, not a portable speed multiplier or an end-to-end jam-rs measurement.

Run the benchmark with:

cargo bench --bench benchmarks

Exhaustive canonical k=21 evidence

To check the hash characteristics and compare it to other hashing functions we did an exhaustive test computing all 2,199,023,255,552 canonical bit-kmers with k >= 21.

Across all 2,199,023,255,552 canonical 21-mers, jamhash_u64 produced 130,928 collision pairs: 99.8901% of the 131,072 pairs expected from a random 64-bit mapping. Every collision was a doublet. The result contains 130,928 duplicate outputs, 261,856 affected inputs, and no multiplicity above two.

Hash Collision pairs Observed / random-map expectation Affected inputs Maximum multiplicity
jamhash_u64 130,928 0.998901 261,856 2
fastmurmur3, seed 42 139,644 1.065399 279,288 2
XXH3 0 N/A (permutation) 0 —
Wang 64-bit mixer 0 N/A (permutation) 0 —

Zero collisions do not imply more random MinHash sampling. The exact XXH3 path above hashes one fixed eight-byte word, and XXH3 is bijective for eight-byte inputs; the Wang mixer is also a 64-bit permutation. Their zero collision counts are therefore structural. FracMinHash instead needs related kmers to behave like reproducible, nearly independent uniform samples around its hash threshold.

The stored aggregate also records zero-inclusive FracMinHash threshold counts and output-bit balance for the complete input. For jamhash_u64, the largest absolute error at the recorded scales 10, 100, 1,000, and 10,000 was 4.728272870180295e-7; the largest recorded output-bit frequency error was 1.0940702850348316e-6. The checked-in verifier recomputes consistency and collision summaries from this aggregate. The raw multi-terabyte hashset is not in this checkout, but a surviving sorted partition was available for the bounded zero-count check.

That posthoc check found exactly 1 zero-valued output from jamhash_u64 across the complete canonical packed k=21 domain. The archived pipeline used only hash < u64::MAX / scale, so its original counts include zero. Current jam-rs uses hash != 0 && hash < u64::MAX / scale; the runtime-equivalent counts therefore subtract one without altering the archived evidence:

Scale Archived, zero-inclusive Runtime-equivalent, zero excluded
10 219,901,285,797 219,901,285,796
100 21,990,205,024 21,990,205,023
1,000 2,199,005,865 2,199,005,864
10,000 219,883,231 219,883,230

The error figures above describe the archived zero-inclusive counts. Measurement provenance and the derived values are in zero_policy.json.

Separate mutation study

A separate, smaller study selected canonical parents satisfying raw & 0x3f == 0. It measured 4,295,504,166,912 single-base substitutions from 68,182,605,824 parents; mutated words were not canonicalized again. With the two-bit encoding above, two of the three possible substitutions at every base change exactly one packed input bit; the third changes two. The test binned the high byte of the wrapping hash(parent) - hash(mutant) difference into 256 equal bins:

Hash Mutation-difference χ² (lower is closer to uniform) Mean output bits changed
jamhash_u64 218.80 31.99999
fastmurmur3, seed 42 251.07 32.00000
XXH3 1,887,897.85 32.00000
Wang 64-bit mixer 3,914,408,417.27 32.05177

For these packed k=21 mutation pairs, jamhash_u64 was near the uniform-difference baseline, while XXH3 and Wang retained strong arithmetic differential structure. XXH3's near-ideal 32-bit mean avalanche shows why collision counts and average avalanche alone do not answer the MinHash question. This result supports jamhash_u64 for threshold sampling of related packed k=21 inputs; it does not by itself establish that XXH3 is unusable.

The mutation observations are dependent. The χ² values are descriptive comparisons, not p-values or direct measures of FracMinHash screening accuracy. The aggregate results are in linear_test.json.

The collision study covered every canonical 21-mer using two-bit A=00, C=01, G=10, T=11 encoding and min(forward, reverse_complement) canonicalization. Its archived aggregate is in combined_metrics.json.

The study used jamhash 0.1.2; version 0.1.3 keeps the identical jamhash_u64 mapping. Exact provenance, immutable source links, artifact hashes, parameters, known environment details, and missing run metadata are recorded in test results and reproducibility. Run the modest posthoc checks with:

python3 scripts/verify_evidence.py

Evidence boundary

  • The exhaustive aggregate establishes the reported collision counts, multiplicities, zero-inclusive threshold counts, and stored distribution summaries for all canonical packed k=21 inputs. Runtime-equivalent threshold counts exclude the single measured zero output.
  • The separate mutation study supports only its reported comparisons for its selected parent rule and non-canonicalized mutations.
  • Neither study measures screening sensitivity, specificity, false-positive or false-negative rates, behavior on labeled biological samples, or deployment safety. Those claims require representative labeled datasets, an evaluation of the complete screening pipeline, and operational validation.
  • Adversarial resistance, collision behavior at other kmer sizes, and architecture-independent byte hashing have not been established.

Secondary APIs

jamhash_bytes and JamHasher support arbitrary bytes and std::hash::Hasher, but they are not covered by the jamhash_u64 mapping promise. Their paths for four or more bytes use native-endian reads, so their outputs are architecture-local and must not be persisted or exchanged between systems.

JamHasher treats each non-empty write call as a separate frame. Split writes generally differ from one concatenated write, empty writes are no-ops, and even a one-write result is not jamhash_bytes. BuildHasherDefault<JamHasher> uses a fixed zero seed. It can be used for trusted, process-local keys, but not for network data, uploaded sequence files, shared databases, or other attacker-controlled keys.

The retained SMHasher3 transcript reports 188/188 passes for a historical JamHash adapter, but its source revision, command, runner version, build flags, and machine were not recorded. It is not evidence that this release was rerun through SMHasher3.

This crate is not cryptographic and provides no adversarial collision or denial-of-service resistance.

Acknowledgements

jamhash draws inspiration from other high-performance non-cryptographic hash implementations, including folded-multiply designs:

  • rapidhash for fast folded multiplication;
  • foldhash for efficient folding techniques;
  • aHash for high-quality non-cryptographic hashing.

License

Licensed under either Apache-2.0 or MIT, at your option. See LICENSE-APACHE and LICENSE-MIT.

About

A fast non-cryptographic hash for packed canonical DNA k-mers

Resources

Stars

4 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages