use crate::matrix::CsrInput;
use std::time::{SystemTime, UNIX_EPOCH};
const MODULUS: u64 = (1u64 << 61) - 1;
const MODULUS_U128: u128 = MODULUS as u128;
#[derive(Clone, Copy, Debug)]
pub struct FingerprintConfig {
pub lanes: usize,
pub seed: u64,
}
impl Default for FingerprintConfig {
fn default() -> Self {
Self { lanes: 3, seed: 0 }
}
}
#[derive(Clone, Debug, Default)]
pub struct FingerprintStats {
pub checks: usize,
pub passes: usize,
pub failures: usize,
pub lanes: usize,
pub seed: u64,
}
#[derive(Clone, Debug)]
pub struct ResidualFingerprint {
row_weights: Vec<Vec<u64>>,
col_weights: Vec<Vec<u64>>,
target: Vec<u64>,
pub seed: u64,
}
impl ResidualFingerprint {
pub fn new<A, B>(a: &A, b: &B, config: FingerprintConfig) -> Self
where
A: CsrInput<Scalar = i64> + ?Sized,
B: CsrInput<Scalar = i64> + ?Sized,
{
assert_eq!(a.cols(), b.rows());
let lanes = config.lanes.max(1);
let seed = if config.seed == 0 {
runtime_seed()
} else {
config.seed
};
let mut row_weights = vec![vec![0u64; a.rows()]; lanes];
let mut col_weights = vec![vec![0u64; b.cols()]; lanes];
for lane in 0..lanes {
let lane_seed = splitmix64(seed ^ (lane as u64).wrapping_mul(0x9E37_79B9_7F4A_7C15));
for i in 0..a.rows() {
row_weights[lane][i] =
nonzero_weight(lane_seed ^ (i as u64).wrapping_mul(0xD1B5_4A32_D192_ED03));
}
for j in 0..b.cols() {
col_weights[lane][j] = nonzero_weight(
lane_seed
^ 0xA076_1D64_78BD_642F
^ (j as u64).wrapping_mul(0xE703_7ED1_A0B4_28DB),
);
}
}
let mut left = vec![vec![0u64; a.cols()]; lanes];
for i in 0..a.rows() {
for (k, av) in a.row(i) {
let avm = signed_mod(av);
for lane in 0..lanes {
left[lane][k] = add_mod(left[lane][k], mul_mod(row_weights[lane][i], avm));
}
}
}
let mut right = vec![vec![0u64; b.rows()]; lanes];
for k in 0..b.rows() {
for (j, bv) in b.row(k) {
let bvm = signed_mod(bv);
for lane in 0..lanes {
right[lane][k] = add_mod(right[lane][k], mul_mod(bvm, col_weights[lane][j]));
}
}
}
let mut target = vec![0u64; lanes];
for lane in 0..lanes {
let mut fp = 0u64;
for k in 0..a.cols() {
fp = add_mod(fp, mul_mod(left[lane][k], right[lane][k]));
}
target[lane] = fp;
}
Self {
row_weights,
col_weights,
target,
seed,
}
}
pub fn lanes(&self) -> usize {
self.target.len()
}
pub fn fingerprint<D>(&self, d: &D) -> Vec<u64>
where
D: CsrInput<Scalar = i64> + ?Sized,
{
assert_eq!(d.rows(), self.row_weights[0].len());
assert_eq!(d.cols(), self.col_weights[0].len());
let lanes = self.lanes();
let mut out = vec![0u64; lanes];
for i in 0..d.rows() {
for (j, value) in d.row(i) {
let vm = signed_mod(value);
for lane in 0..lanes {
let term = mul_mod(
mul_mod(self.row_weights[lane][i], vm),
self.col_weights[lane][j],
);
out[lane] = add_mod(out[lane], term);
}
}
}
out
}
#[inline]
pub fn verifies<D>(&self, d: &D) -> bool
where
D: CsrInput<Scalar = i64> + ?Sized,
{
self.fingerprint(d) == self.target
}
pub fn target(&self) -> &[u64] {
&self.target
}
}
#[inline]
fn add_mod(a: u64, b: u64) -> u64 {
let x = a + b;
if x >= MODULUS {
x - MODULUS
} else {
x
}
}
#[inline]
fn reduce_mersenne(mut x: u128) -> u64 {
x = (x & MODULUS_U128) + (x >> 61);
x = (x & MODULUS_U128) + (x >> 61);
x = (x & MODULUS_U128) + (x >> 61);
let mut r = x as u64;
if r >= MODULUS {
r -= MODULUS;
}
if r >= MODULUS {
r -= MODULUS;
}
r
}
#[inline]
fn mul_mod(a: u64, b: u64) -> u64 {
reduce_mersenne(a as u128 * b as u128)
}
#[inline]
fn signed_mod(v: i64) -> u64 {
if v >= 0 {
reduce_mersenne(v as u128)
} else {
let mag = (-(v as i128)) as u128;
let r = reduce_mersenne(mag);
if r == 0 {
0
} else {
MODULUS - r
}
}
}
#[inline]
fn nonzero_weight(x: u64) -> u64 {
1 + splitmix64(x) % (MODULUS - 1)
}
fn runtime_seed() -> u64 {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0xC0FF_EE00_D15C_A11E);
splitmix64(nanos ^ (std::process::id() as u64).rotate_left(17))
}
#[inline]
fn splitmix64(mut x: u64) -> u64 {
x = x.wrapping_add(0x9E37_79B9_7F4A_7C15);
x = (x ^ (x >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
x = (x ^ (x >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
x ^ (x >> 31)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::matrix::CsrMatrix;
use crate::spgemm::spgemm_hash;
#[test]
fn fingerprint_accepts_exact_product_and_rejects_perturbation() {
let a = CsrMatrix::from_triplets(3, 3, &[(0, 0, 2), (0, 2, 1), (1, 1, -3), (2, 0, 5)]);
let b = CsrMatrix::from_triplets(3, 3, &[(0, 0, 7), (1, 2, 11), (2, 1, 13)]);
let (c, _) = spgemm_hash(&a, &b);
let fp = ResidualFingerprint::new(&a, &b, FingerprintConfig { lanes: 3, seed: 42 });
assert!(fp.verifies(&c));
let mut t = Vec::new();
for i in 0..c.rows {
for (j, v) in c.row(i) {
t.push((i, j, v));
}
}
t.push((2, 2, 1));
let bad = CsrMatrix::from_triplets(c.rows, c.cols, &t);
assert!(!fp.verifies(&bad));
}
#[test]
fn fast_mersenne_reduction_matches_reference_modulus() {
let values = [
0u128,
1,
MODULUS as u128 - 1,
MODULUS as u128,
(MODULUS as u128) * (MODULUS as u128),
((1u128 << 122) - 1),
(i64::MAX as u128) * (MODULUS as u128 - 7),
];
for x in values {
assert_eq!(reduce_mersenne(x), (x % MODULUS_U128) as u64, "x={x}");
}
}
#[test]
fn signed_mod_matches_reference_for_extremes() {
for v in [i64::MIN, -9, -1, 0, 1, 9, i64::MAX] {
let reference = if v >= 0 {
(v as u128 % MODULUS_U128) as u64
} else {
let m = (-(v as i128)) as u128 % MODULUS_U128;
if m == 0 {
0
} else {
MODULUS - m as u64
}
};
assert_eq!(signed_mod(v), reference, "v={v}");
}
}
}