use super::*;
use crate::core::ask::jaccard_similarity;
fn all_pairs_reference(spans: &[AskSpan]) -> Vec<AskSpan> {
let term_sets: Vec<_> = spans
.iter()
.map(|span| tokenize_for_ask(&span.text))
.collect();
let mut order: Vec<_> = (0..spans.len()).collect();
order.sort_by(|&left, &right| {
spans[right]
.score
.total_cmp(&spans[left].score)
.then_with(|| spans[left].memory_id.cmp(&spans[right].memory_id))
.then_with(|| spans[left].byte_start.cmp(&spans[right].byte_start))
});
let mut assigned = vec![false; spans.len()];
let mut representatives = Vec::new();
for &seed in &order {
if assigned[seed] {
continue;
}
assigned[seed] = true;
let mut supporting = BTreeSet::from([spans[seed].memory_id.as_str()]);
for &other in &order {
if assigned[other]
|| has_negation(&spans[seed].text) != has_negation(&spans[other].text)
{
continue;
}
if jaccard_similarity(&term_sets[seed], &term_sets[other])
>= CLUSTER_SIMILARITY_THRESHOLD
{
assigned[other] = true;
supporting.insert(spans[other].memory_id.as_str());
}
}
let multiplier = (1.0 + 0.1 * (supporting.len() as f32).ln()).min(CORROBORATION_CAP);
let mut representative = spans[seed].clone();
representative.score = (representative.score * multiplier).clamp(0.0, 1.0);
representatives.push(representative);
}
representatives.sort_by(|left, right| {
right
.score
.partial_cmp(&left.score)
.unwrap_or(std::cmp::Ordering::Equal)
.then_with(|| left.memory_id.cmp(&right.memory_id))
});
representatives
}
fn span(index: usize, memory: usize, text: &str, score: f32) -> AskSpan {
AskSpan {
memory_id: format!("memory-{memory:05}"),
byte_start: index * 100,
byte_end: index * 100 + text.len(),
text: text.to_owned(),
score,
trust_class: "human_explicit".to_owned(),
memory_confidence: 0.9,
provenance_uri: Some(format!("manual://sparse-clustering/{memory}")),
team_provenance: None,
}
}
fn signature(spans: &[AskSpan]) -> serde_json::Value {
serde_json::json!(
spans
.iter()
.map(|span| serde_json::json!({
"memory": span.memory_id,
"start": span.byte_start,
"end": span.byte_end,
"text": span.text,
"scoreBits": span.score.to_bits(),
"confidenceBits": span.memory_confidence.to_bits(),
"trust": span.trust_class,
"provenance": span.provenance_uri,
"team": span.team_provenance.as_ref().map(|value| value.to_json()),
}))
.collect::<Vec<_>>()
)
}
#[test]
fn agrees_bit_for_bit_with_all_pairs_on_seeded_corpora() {
let vocabulary = [
"cargo",
"fmt",
"release",
"cache",
"delta",
"port",
"database",
"tls",
"migration",
"audit",
"café",
"the",
"not",
"alpha",
"beta",
];
let mut state = 0x7289_d115_0831_abc7_u64;
for round in 0..64 {
let mut input = Vec::new();
for index in 0..48 {
state = state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
let length = (state as usize % 9) + 1;
let mut words = Vec::new();
for _ in 0..length {
state = state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
words.push(vocabulary[state as usize % vocabulary.len()]);
}
let score = ((state >> 32) % 101) as f32 / 100.0;
input.push(span(index, index / 2, &words.join(" "), score));
}
assert_eq!(
signature(&cluster_spans(&input)),
signature(&all_pairs_reference(&input)),
"corpus {round}"
);
input.reverse();
assert_eq!(
signature(&cluster_spans(&input)),
signature(&all_pairs_reference(&input)),
"reversed corpus {round}"
);
}
}
#[test]
fn opposition_and_same_memory_repetition_do_not_inflate_corroboration() {
let input = vec![
span(0, 0, "Run cargo fmt before release.", 0.7),
span(1, 0, "Run cargo fmt before release.", 0.7),
span(2, 1, "Run cargo fmt before release.", 0.7),
span(3, 2, "Do not run cargo fmt before release.", 0.7),
];
let actual = cluster_spans(&input);
assert_eq!(signature(&actual), signature(&all_pairs_reference(&input)));
assert_eq!(actual.len(), 2);
assert!(actual.iter().any(|span| has_negation(&span.text)));
assert_eq!(
actual[0].score.to_bits(),
(0.7_f32 * (1.0 + 0.1 * 2.0_f32.ln())).to_bits()
);
}
#[test]
fn shared_vocabulary_does_not_turn_seed_clustering_into_transitive_closure() {
let input = vec![
span(0, 0, "alpha beta gamma delta epsilon", 0.9),
span(1, 1, "alpha beta gamma delta epsilon zeta", 0.8),
span(2, 2, "alpha beta gamma delta epsilon zeta theta", 0.7),
];
let actual = cluster_spans(&input);
assert_eq!(signature(&actual), signature(&all_pairs_reference(&input)));
assert_eq!(actual.len(), 2); }
#[test]
fn ten_thousand_disjoint_spans_need_zero_pairwise_similarity_checks() {
let input: Vec<_> = (0..10_000)
.map(|index| span(index, index, &format!("unique_term_{index}"), 0.5))
.collect();
let mut comparisons = 0;
let actual = cluster_with_observer(&input, || comparisons += 1);
assert_eq!(comparisons, 0);
assert_eq!(
actual.len(),
input.len(),
"no evidence was truncated to get the work reduction"
);
assert_eq!(actual[0].memory_id, "memory-00000");
assert_eq!(actual[9_999].memory_id, "memory-09999");
assert!(
actual
.iter()
.all(|span| span.score.to_bits() == 0.5_f32.to_bits())
);
}
#[test]
fn repeated_words_count_once_and_empty_term_sets_stay_independent() {
let input = vec![
span(0, 0, "cargo cargo fmt release", 0.6),
span(1, 1, "cargo fmt fmt release release", 0.6),
span(2, 2, "the and of", 0.6),
span(3, 3, "a an the", 0.6),
span(4, 4, "🦀", 0.6),
];
assert_eq!(
signature(&cluster_spans(&input)),
signature(&all_pairs_reference(&input))
);
assert_eq!(cluster_spans(&input).len(), 4);
assert!(cluster_spans(&[]).is_empty());
}
#[test]
fn input_permutations_preserve_citations_and_exact_score_bits() {
let mut input: Vec<_> = (0..40)
.map(|index| {
span(
index,
index,
if index % 3 == 0 {
"Do not run cargo fmt before release."
} else {
"Run cargo fmt before release."
},
0.6,
)
})
.collect();
let expected = signature(&cluster_spans(&input));
input.reverse();
assert_eq!(signature(&cluster_spans(&input)), expected);
input.rotate_left(17);
assert_eq!(signature(&cluster_spans(&input)), expected);
}
#[test]
fn opposite_polarities_are_never_pairwise_compared() {
let input: Vec<_> = (0..100)
.map(|index| {
span(
index,
index,
if index % 2 == 0 {
"Run cargo fmt before release."
} else {
"Do not run cargo fmt before release."
},
0.6,
)
})
.collect();
let mut comparisons = 0;
let actual = cluster_with_observer(&input, || comparisons += 1);
assert_eq!(comparisons, 98);
assert_eq!(signature(&actual), signature(&all_pairs_reference(&input)));
}