use hippmem_core::config::AlgoParams;
use hippmem_core::model::links::{
AssociationKeys, LexicalSignature, MatchDimension, SemanticSignature,
};
use hippmem_write::candidates::discover_candidates;
use hippmem_write::scoring::associate_score;
fn make_keys(entity_count: u64, topic_count: u64) -> AssociationKeys {
use hippmem_core::ids::{EntityKey, TopicKey};
AssociationKeys {
entity_keys: (0..entity_count)
.map(|i| i + 100)
.collect::<Vec<EntityKey>>(),
temporal_keys: vec![42],
lexical_signature: LexicalSignature {
simhash: [1, 2, 3, 4],
},
semantic_signature: SemanticSignature {
lexical_simhash: [1, 2, 3, 4],
dense_embedding_ref: None,
binary_code: [0xABCD, 0x1234],
topic_minhash: [0u32; 16],
},
topic_keys: (0..topic_count).map(|i| i + 200).collect::<Vec<TopicKey>>(),
emotion_keys: vec![],
goal_keys: vec![300],
event_keys: vec![],
causal_keys: vec![],
}
}
#[test]
fn candidate_discovery_multi_dim() {
let a = make_keys(3, 2);
let b = make_keys(3, 2);
let result = discover_candidates(&a, &b);
let matched_dims = result.matched_dimensions;
assert!(matched_dims.len() >= 2, "at least 2 dimensions should hit");
}
#[test]
fn no_shared_dimensions_zero_score() {
let zero_sig = SemanticSignature {
lexical_simhash: [0; 4],
dense_embedding_ref: None,
binary_code: [0, 0],
topic_minhash: [0u32; 16],
};
let a = AssociationKeys {
entity_keys: vec![1],
temporal_keys: vec![1],
lexical_signature: LexicalSignature { simhash: [0; 4] },
semantic_signature: zero_sig.clone(),
topic_keys: vec![10],
emotion_keys: vec![],
goal_keys: vec![],
event_keys: vec![],
causal_keys: vec![],
};
let b = AssociationKeys {
entity_keys: vec![999],
temporal_keys: vec![99],
lexical_signature: LexicalSignature { simhash: [0; 4] },
semantic_signature: zero_sig,
topic_keys: vec![888],
emotion_keys: vec![],
goal_keys: vec![],
event_keys: vec![],
causal_keys: vec![],
};
let result = discover_candidates(&a, &b);
assert!(
result.matched_dimensions.is_empty(),
"no shared dimensions should not hit"
);
let score = associate_score(&result, 0, 1000, &AlgoParams::default());
assert!(
score.value() < 0.1,
"score with no shared dimensions should be low"
);
}
#[test]
fn multi_dim_bonus() {
let a = AssociationKeys {
entity_keys: vec![1, 2, 3],
temporal_keys: vec![100],
topic_keys: vec![10, 20],
goal_keys: vec![5],
..make_keys(0, 0)
};
let b = AssociationKeys {
entity_keys: vec![1, 2, 3], temporal_keys: vec![100], topic_keys: vec![10, 20], goal_keys: vec![5], ..a.clone()
};
let result = discover_candidates(&a, &b);
assert!(result.matched_dimensions.len() >= 3);
}
#[test]
fn score_in_expected_range() {
let a = make_keys(3, 2);
let b = make_keys(3, 2);
let result = discover_candidates(&a, &b);
let score = associate_score(&result, 2, 1000, &AlgoParams::default());
let v = score.value();
assert!((0.0..=1.0).contains(&v), "score {v} should be in [0,1]");
}
use hippmem_write::candidates::CandidateResult;
fn single_dim_cand(dim: MatchDimension) -> CandidateResult {
let mut c = CandidateResult {
matched_dimensions: vec![dim],
entity_jaccard: 0.0,
topic_jaccard: 0.0,
temporal_overlap: 0,
goal_jaccard: 0.0,
event_jaccard: 0.0,
causal_overlap: 0,
emotion_overlap: 0,
importance_value: 0.0,
co_context_score: 0.0,
lexical_similarity: 0.0,
semantic_binary_similarity: 0.0,
};
match dim {
MatchDimension::Entity => c.entity_jaccard = 0.5,
MatchDimension::Topic => c.topic_jaccard = 0.5,
MatchDimension::Temporal => c.temporal_overlap = 1,
MatchDimension::Goal => c.goal_jaccard = 0.5,
MatchDimension::Event => c.event_jaccard = 0.5,
MatchDimension::Causal => c.causal_overlap = 1,
MatchDimension::Semantic => c.lexical_similarity = 0.5,
_ => {}
}
c
}
#[test]
fn weight_entity_exact() {
let params = AlgoParams::default();
let c = single_dim_cand(MatchDimension::Entity);
let s = associate_score(&c, 1, 1000, ¶ms);
assert!(
(s.value() - 0.10).abs() < 0.001,
"entity: expected 0.10, got {}",
s.value()
);
}
#[test]
fn weight_topic_exact() {
let params = AlgoParams::default();
let c = single_dim_cand(MatchDimension::Topic);
let s = associate_score(&c, 1, 1000, ¶ms);
assert!(
(s.value() - 0.05).abs() < 0.001,
"topic: expected 0.05, got {}",
s.value()
);
}
#[test]
fn weight_goal_exact() {
let params = AlgoParams::default();
let c = single_dim_cand(MatchDimension::Goal);
let s = associate_score(&c, 1, 1000, ¶ms);
assert!(
(s.value() - 0.06).abs() < 0.001,
"goal: expected 0.06, got {}",
s.value()
);
}
#[test]
fn weight_event_exact() {
let params = AlgoParams::default();
let c = single_dim_cand(MatchDimension::Event);
let s = associate_score(&c, 1, 1000, ¶ms);
assert!(
(s.value() - 0.05).abs() < 0.001,
"event: expected 0.05, got {}",
s.value()
);
}
#[test]
fn weight_temporal_exact() {
let params = AlgoParams::default();
let c = single_dim_cand(MatchDimension::Temporal);
let s = associate_score(&c, 1, 1000, ¶ms);
assert!(
(s.value() - 0.10).abs() < 0.001,
"temporal: expected 0.10, got {}",
s.value()
);
}
#[test]
fn weight_causal_exact() {
let params = AlgoParams::default();
let c = single_dim_cand(MatchDimension::Causal);
let s = associate_score(&c, 1, 1000, ¶ms);
assert!(
(s.value() - 0.10).abs() < 0.001,
"causal: expected 0.10, got {}",
s.value()
);
}
#[test]
fn weight_emotion_exact() {
let params = AlgoParams::default();
let c = CandidateResult {
matched_dimensions: vec![MatchDimension::Emotion],
entity_jaccard: 0.0,
topic_jaccard: 0.0,
temporal_overlap: 0,
goal_jaccard: 0.0,
event_jaccard: 0.0,
causal_overlap: 0,
emotion_overlap: 2, importance_value: 0.0,
co_context_score: 0.0,
lexical_similarity: 0.0,
semantic_binary_similarity: 0.0,
};
let s = associate_score(&c, 1, 1000, ¶ms);
assert!(
(s.value() - 0.05).abs() < 0.001,
"emotion: expected 0.05, got {}",
s.value()
);
}
#[test]
fn weight_importance_exact() {
let params = AlgoParams::default();
let c = CandidateResult {
matched_dimensions: vec![MatchDimension::Importance],
entity_jaccard: 0.0,
topic_jaccard: 0.0,
temporal_overlap: 0,
goal_jaccard: 0.0,
event_jaccard: 0.0,
causal_overlap: 0,
emotion_overlap: 0,
importance_value: 0.8,
co_context_score: 0.0,
lexical_similarity: 0.0,
semantic_binary_similarity: 0.0,
};
let s = associate_score(&c, 1, 1000, ¶ms);
assert!(
(s.value() - 0.016).abs() < 0.001,
"importance: expected 0.016, got {}",
s.value()
);
}
#[test]
fn weight_context_exact() {
let params = AlgoParams::default();
let c = CandidateResult {
matched_dimensions: vec![MatchDimension::CoContext],
entity_jaccard: 0.0,
topic_jaccard: 0.0,
temporal_overlap: 0,
goal_jaccard: 0.0,
event_jaccard: 0.0,
causal_overlap: 0,
emotion_overlap: 0,
importance_value: 0.0,
co_context_score: 0.7,
lexical_similarity: 0.0,
semantic_binary_similarity: 0.0,
};
let s = associate_score(&c, 1, 1000, ¶ms);
assert!(
(s.value() - 0.021).abs() < 0.001,
"context: expected 0.021, got {}",
s.value()
);
}
fn multi_dim_cand_only(dims: Vec<MatchDimension>) -> CandidateResult {
let mut c = CandidateResult {
matched_dimensions: dims,
entity_jaccard: 0.0,
topic_jaccard: 0.0,
temporal_overlap: 0,
goal_jaccard: 0.0,
event_jaccard: 0.0,
causal_overlap: 0,
emotion_overlap: 0,
importance_value: 0.0,
co_context_score: 0.0,
lexical_similarity: 0.0,
semantic_binary_similarity: 0.0,
};
for dim in &c.matched_dimensions {
match dim {
MatchDimension::Entity => c.entity_jaccard = 0.5,
MatchDimension::Topic => c.topic_jaccard = 0.5,
MatchDimension::Temporal => c.temporal_overlap = 1,
MatchDimension::Goal => c.goal_jaccard = 0.5,
MatchDimension::Event => c.event_jaccard = 0.5,
_ => {}
}
}
c
}
#[test]
fn multi_dim_bonus_2_dims_custom_threshold() {
let params = AlgoParams {
multi_dim_min_dims: 2,
..Default::default()
};
let c = multi_dim_cand_only(vec![MatchDimension::Entity, MatchDimension::Topic]);
let s = associate_score(&c, 2, 1000, ¶ms);
let expected = 0.20 * 0.5 + 0.10 * 0.5 + 0.15;
assert!(
(s.value() - expected).abs() < 0.001,
"2-dims: expected {expected}, got {}",
s.value()
);
}
#[test]
fn multi_dim_bonus_3_dims_default() {
let params = AlgoParams::default(); let c = multi_dim_cand_only(vec![
MatchDimension::Entity,
MatchDimension::Topic,
MatchDimension::Temporal,
]);
let s = associate_score(&c, 3, 1000, ¶ms);
let expected = 0.20 * 0.5 + 0.10 * 0.5 + 0.10 * (1.0 / 3.0) + 0.15;
assert!(
(s.value() - expected).abs() < 0.001,
"3-dims: expected {expected}, got {}",
s.value()
);
}
#[test]
fn multi_dim_bonus_4_dims() {
let params = AlgoParams::default();
let c = multi_dim_cand_only(vec![
MatchDimension::Entity,
MatchDimension::Topic,
MatchDimension::Temporal,
MatchDimension::Goal,
]);
let s = associate_score(&c, 4, 1000, ¶ms);
let expected = 0.20 * 0.5 + 0.10 * 0.5 + 0.10 * (1.0 / 4.0) + 0.12 * 0.5 + 0.15;
assert!(
(s.value() - expected).abs() < 0.001,
"4-dims: expected {expected}, got {}",
s.value()
);
}
#[test]
fn multi_dim_bonus_5_dims() {
let params = AlgoParams::default();
let c = multi_dim_cand_only(vec![
MatchDimension::Entity,
MatchDimension::Topic,
MatchDimension::Temporal,
MatchDimension::Goal,
MatchDimension::Event,
]);
let s = associate_score(&c, 5, 1000, ¶ms);
let expected = 0.20 * 0.5 + 0.10 * 0.5 + 0.10 * (1.0 / 5.0) + 0.12 * 0.5 + 0.10 * 0.5 + 0.15;
assert!(
(s.value() - expected).abs() < 0.001,
"5-dims: expected {expected}, got {}",
s.value()
);
}
#[test]
fn single_semantic_penalty_cold_start() {
let params = AlgoParams::default();
let c = CandidateResult {
matched_dimensions: vec![MatchDimension::Semantic],
entity_jaccard: 0.0,
topic_jaccard: 0.0,
temporal_overlap: 0,
goal_jaccard: 0.0,
event_jaccard: 0.0,
causal_overlap: 0,
emotion_overlap: 0,
importance_value: 0.0,
co_context_score: 0.0,
lexical_similarity: 0.5,
semantic_binary_similarity: 0.0,
};
let s_cold = associate_score(&c, 1, 100, ¶ms);
let expected_cold = 0.18 * 0.5 * 0.6;
assert!(
(s_cold.value() - expected_cold).abs() < 0.001,
"semantic+cold_start: expected {expected_cold}, got {}",
s_cold.value()
);
let s_warm = associate_score(&c, 1, 1000, ¶ms);
let expected_warm = 0.18 * 0.5 * 1.0;
assert!(
(s_warm.value() - expected_warm).abs() < 0.001,
"semantic+warm: expected {expected_warm}, got {}",
s_warm.value()
);
}
#[test]
fn score_clamped_to_one() {
let params = AlgoParams::default();
let c = CandidateResult {
matched_dimensions: vec![
MatchDimension::Entity,
MatchDimension::Topic,
MatchDimension::Temporal,
MatchDimension::Goal,
MatchDimension::Event,
],
entity_jaccard: 1.0,
topic_jaccard: 1.0,
temporal_overlap: 10,
goal_jaccard: 1.0,
event_jaccard: 1.0,
causal_overlap: 0,
emotion_overlap: 0,
importance_value: 1.0,
co_context_score: 1.0,
lexical_similarity: 1.0,
semantic_binary_similarity: 1.0,
};
let s = associate_score(&c, 10, 1000, ¶ms);
assert!(
s.value() <= 1.0,
"score should not exceed 1.0, got={}",
s.value()
);
}
#[test]
fn score_not_negative() {
let params = AlgoParams::default();
let c = CandidateResult {
matched_dimensions: vec![],
entity_jaccard: 0.0,
topic_jaccard: 0.0,
temporal_overlap: 0,
goal_jaccard: 0.0,
event_jaccard: 0.0,
causal_overlap: 0,
emotion_overlap: 0,
importance_value: 0.0,
co_context_score: 0.0,
lexical_similarity: 0.0,
semantic_binary_similarity: 0.0,
};
let s = associate_score(&c, 0, 1000, ¶ms);
assert!(
s.value() >= 0.0,
"score should not be negative, got={}",
s.value()
);
}
#[test]
fn lexical_only_with_other_dims() {
let params = AlgoParams::default();
let c_empty = CandidateResult {
matched_dimensions: vec![],
entity_jaccard: 0.0,
topic_jaccard: 0.0,
temporal_overlap: 0,
goal_jaccard: 0.0,
event_jaccard: 0.0,
causal_overlap: 0,
emotion_overlap: 0,
importance_value: 0.0,
co_context_score: 0.0,
lexical_similarity: 1.0, semantic_binary_similarity: 1.0, };
let s = associate_score(&c_empty, 0, 1000, ¶ms);
assert_eq!(
s.value(),
0.0,
"lexical/binary should not participate without other dimensions"
);
let c_has = CandidateResult {
matched_dimensions: vec![MatchDimension::Entity],
entity_jaccard: 0.0, topic_jaccard: 0.0,
temporal_overlap: 0,
goal_jaccard: 0.0,
event_jaccard: 0.0,
causal_overlap: 0,
emotion_overlap: 0,
importance_value: 0.0,
co_context_score: 0.0,
lexical_similarity: 0.5,
semantic_binary_similarity: 0.0,
};
let s2 = associate_score(&c_has, 1, 1000, ¶ms);
assert!(
s2.value() > 0.0,
"lexical should participate when other dimensions are present"
);
assert!(
(s2.value() - 0.09).abs() < 0.001,
"lexical: expected 0.09, got {}",
s2.value()
);
}