prism-q 0.35.0

Fast Rust quantum circuit simulator. OpenQASM 3.0, multiple backends, AVX2 SIMD kernels, optional CUDA and MPI, QEC tooling, Python bindings.
Documentation
//! Integer-keyed hashing for hot maps.
//!
//! Keys are basis-state indices and packed measurement words, never untrusted input,
//! so SipHash's collision resistance buys nothing. This hash costs a rotate, an xor
//! and a multiply per word.

use std::collections::HashMap;
use std::hash::{BuildHasher, Hasher};

pub(crate) struct FxHasher {
    hash: u64,
}

impl FxHasher {
    const SEED: u64 = 0x517cc1b727220a95;
}

impl Hasher for FxHasher {
    /// The multiply-xor step drives entropy toward the high bits while the
    /// table takes its bucket index from the low ones, so structured keys
    /// cluster. Basis-state indices from a structured circuit are exactly
    /// that, and the clustering grows with the key count: without this
    /// finalizer a fixture holding every index of a 20-qubit register in the
    /// map regressed 11.8% while the high-entropy `sparse/random_d10` rows
    /// were unaffected. The `sparse/densify` rows sweep the same key-count
    /// growth.
    #[inline]
    fn finish(&self) -> u64 {
        let mut h = self.hash;
        h ^= h >> 33;
        h = h.wrapping_mul(0xff51_afd7_ed55_8ccd);
        h ^= h >> 33;
        h
    }

    #[inline]
    fn write(&mut self, bytes: &[u8]) {
        for chunk in bytes.chunks(8) {
            let mut buf = [0u8; 8];
            buf[..chunk.len()].copy_from_slice(chunk);
            let word = u64::from_ne_bytes(buf);
            self.hash = (self.hash.rotate_left(5) ^ word).wrapping_mul(Self::SEED);
        }
    }

    #[inline]
    fn write_u64(&mut self, i: u64) {
        self.hash = (self.hash.rotate_left(5) ^ i).wrapping_mul(Self::SEED);
    }

    #[inline]
    fn write_usize(&mut self, i: usize) {
        self.hash = (self.hash.rotate_left(5) ^ i as u64).wrapping_mul(Self::SEED);
    }
}

#[derive(Clone, Default)]
pub(crate) struct FxBuildHasher;

impl BuildHasher for FxBuildHasher {
    type Hasher = FxHasher;

    fn build_hasher(&self) -> FxHasher {
        FxHasher { hash: 0 }
    }
}

pub(crate) type FxHashMap<K, V> = HashMap<K, V, FxBuildHasher>;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn fx_hasher_finish_changes_with_writes() {
        let mut h1 = FxBuildHasher.build_hasher();
        h1.write(&[1, 2, 3, 4, 5, 6, 7, 8, 9]);
        let v1 = h1.finish();
        let mut h2 = FxBuildHasher.build_hasher();
        h2.write_u64(42);
        h2.write_usize(7);
        let v2 = h2.finish();
        assert_ne!(v1, 0);
        assert_ne!(v2, 0);
        assert_ne!(v1, v2);
    }
}