candding 0.1.0

Pure candle embeddings for Rust with no ONNX: dense, sparse, multi-vector and rerank models today, image models on the roadmap
Documentation
use super::*;

/// Query rows `[1,0]` and `[0,1]` each max at 1 against the three document rows, summing to 2.0.
#[test]
fn hand_computed_score_matches_the_maxsim_formula() {
  let query = MultiVectorEmbedding::from_rows(2, [vec![1.0, 0.0], vec![0.0, 1.0]]);
  let document =
    MultiVectorEmbedding::from_rows(2, [vec![1.0, 0.0], vec![0.0, 1.0], vec![1.0, 1.0]]);
  assert_eq!(
    late_interaction_score(&query, None, &document, None, MaskedRowFill::DtypeMinimum),
    2.0
  );
}

/// Swapped, each of the three rows maxes at 1, summing to 3.0 over three terms instead of two.
#[test]
fn swapping_query_and_document_changes_the_score() {
  let a = MultiVectorEmbedding::from_rows(2, [vec![1.0, 0.0], vec![0.0, 1.0]]);
  let b = MultiVectorEmbedding::from_rows(2, [vec![1.0, 0.0], vec![0.0, 1.0], vec![1.0, 1.0]]);
  assert_eq!(
    late_interaction_score(&a, None, &b, None, MaskedRowFill::DtypeMinimum),
    2.0
  );
  assert_eq!(
    late_interaction_score(&b, None, &a, None, MaskedRowFill::DtypeMinimum),
    3.0
  );
}

/// The padding row `[100, 0]` would score 100.0 unmasked; masked, the two real rows give 0.5.
#[test]
fn document_side_padding_never_wins_a_maximum() {
  let query = MultiVectorEmbedding::from_rows(2, [vec![1.0, 0.0]]);
  let document =
    MultiVectorEmbedding::from_rows(2, [vec![0.0, 1.0], vec![0.5, 0.0], vec![100.0, 0.0]]);
  let masked = late_interaction_score(
    &query,
    None,
    &document,
    Some(&[true, true, false]),
    MaskedRowFill::DtypeMinimum,
  );
  assert_eq!(masked, 0.5);
  let unmasked = late_interaction_score(&query, None, &document, None, MaskedRowFill::DtypeMinimum);
  assert_eq!(
    unmasked, 100.0,
    "the padding row would dominate the maximum if it were not excluded"
  );
}

/// With every real row negative a `Zero` fill wins and a `DtypeMinimum` fill cannot, so a hardcoded fill fails one.
#[test]
fn dtype_minimum_and_zero_fill_disagree_when_every_real_row_scores_negative() {
  let query = MultiVectorEmbedding::from_rows(2, [vec![1.0, 0.0]]);
  let document = MultiVectorEmbedding::from_rows(2, [vec![-1.0, 0.0], vec![10.0, 10.0]]);
  let mask = [true, false];
  let dtype_min = late_interaction_score(
    &query,
    None,
    &document,
    Some(&mask),
    MaskedRowFill::DtypeMinimum,
  );
  let zero = late_interaction_score(&query, None, &document, Some(&mask), MaskedRowFill::Zero);
  assert_eq!(
    dtype_min, -1.0,
    "the real, negative row must still win over a dtype-minimum-filled masked row"
  );
  assert_eq!(
    zero, 0.0,
    "the masked row, filled with zero, beats the real negative row"
  );
  assert_ne!(dtype_min, zero);
}

/// The padding row `[1,1]` would add its own 1.0 unmasked: 3.0 instead of the real rows' 2.0.
#[test]
fn query_side_padding_contributes_nothing_to_the_sum() {
  let query = MultiVectorEmbedding::from_rows(2, [vec![1.0, 0.0], vec![0.0, 1.0], vec![1.0, 1.0]]);
  let document = MultiVectorEmbedding::from_rows(2, [vec![1.0, 0.0], vec![0.0, 1.0]]);
  let masked = late_interaction_score(
    &query,
    Some(&[true, true, false]),
    &document,
    None,
    MaskedRowFill::DtypeMinimum,
  );
  assert_eq!(masked, 2.0);
  let unmasked = late_interaction_score(&query, None, &document, None, MaskedRowFill::DtypeMinimum);
  assert_eq!(
    unmasked, 3.0,
    "the padding row would add its own maximum to the sum if it were not excluded"
  );
}

/// The widths differ on purpose: the document's emptiness is checked before either `dim()` is read.
#[test]
fn two_empty_matrices_score_the_empty_document_sentinel_even_with_mismatched_dims() {
  let query = MultiVectorEmbedding::from_rows(3, Vec::<Vec<f32>>::new());
  let document = MultiVectorEmbedding::from_rows(7, Vec::<Vec<f32>>::new());
  assert_eq!(
    late_interaction_score(&query, None, &document, None, MaskedRowFill::DtypeMinimum),
    -1e9
  );
}

#[test]
fn an_empty_query_against_a_real_document_sums_to_zero() {
  let query = MultiVectorEmbedding::from_rows(2, Vec::<Vec<f32>>::new());
  let document = MultiVectorEmbedding::from_rows(2, [vec![1.0, 1.0]]);
  assert_eq!(
    late_interaction_score(&query, None, &document, None, MaskedRowFill::DtypeMinimum),
    0.0
  );
}

#[test]
fn a_real_query_against_an_empty_document_scores_the_empty_document_sentinel() {
  let query = MultiVectorEmbedding::from_rows(2, [vec![1.0, 0.0]]);
  let document = MultiVectorEmbedding::from_rows(2, Vec::<Vec<f32>>::new());
  assert_eq!(
    late_interaction_score(&query, None, &document, None, MaskedRowFill::DtypeMinimum),
    -1e9
  );
}

#[test]
#[should_panic(expected = "query mask length must match its row count")]
fn a_mismatched_query_mask_length_panics() {
  let query = MultiVectorEmbedding::from_rows(2, [vec![1.0, 0.0]]);
  let document = MultiVectorEmbedding::from_rows(2, [vec![1.0, 0.0]]);
  late_interaction_score(
    &query,
    Some(&[true, true]),
    &document,
    None,
    MaskedRowFill::DtypeMinimum,
  );
}

#[test]
#[should_panic(expected = "late interaction compares rows of equal width")]
fn comparing_different_dims_panics_when_there_is_something_real_to_compare() {
  let query = MultiVectorEmbedding::from_rows(2, [vec![1.0, 0.0]]);
  let document = MultiVectorEmbedding::from_rows(3, [vec![1.0, 0.0, 0.0]]);
  late_interaction_score(&query, None, &document, None, MaskedRowFill::DtypeMinimum);
}