use std::hash::Hash;
use crate::common::types::ScoreType;
use itertools::Itertools;
use ordered_float::OrderedFloat;
use serde::Serialize;
use super::{Query, TransformInto};
use crate::segment::common::operation_error::OperationResult;
#[derive(Clone, Debug, Serialize, Hash, PartialEq)]
pub struct FeedbackItem<T> {
pub vector: T,
pub score: OrderedFloat<ScoreType>,
}
impl<T> FeedbackItem<T> {
pub fn transform<F, U>(self, mut f: F) -> OperationResult<FeedbackItem<U>>
where
F: FnMut(T) -> OperationResult<U>,
{
Ok(FeedbackItem {
vector: f(self.vector)?,
score: self.score,
})
}
}
#[derive(Clone, Debug, Serialize, Hash, PartialEq)]
pub struct NaiveFeedbackQuery<T> {
pub target: T,
pub feedback: Vec<FeedbackItem<T>>,
pub coefficients: NaiveFeedbackCoefficients,
}
impl<T: Clone> NaiveFeedbackQuery<T> {
pub fn into_query(self) -> FeedbackQuery<T> {
FeedbackQuery::new(self.target, self.feedback, self.coefficients)
}
}
impl<T> NaiveFeedbackQuery<T> {
pub fn flat_iter(&self) -> impl Iterator<Item = &T> {
self.feedback
.iter()
.map(|item| &item.vector)
.chain(std::iter::once(&self.target))
}
}
impl<T, U> TransformInto<NaiveFeedbackQuery<U>, T, U> for NaiveFeedbackQuery<T> {
fn transform<F>(self, mut f: F) -> OperationResult<NaiveFeedbackQuery<U>>
where
F: FnMut(T) -> OperationResult<U>,
{
let Self {
target,
feedback,
coefficients,
} = self;
Ok(NaiveFeedbackQuery {
target: f(target)?,
feedback: feedback
.into_iter()
.map(|item| item.transform(&mut f))
.try_collect()?,
coefficients,
})
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Hash)]
pub struct ContextPair<T> {
pub positive: T,
pub negative: T,
pub partial_computation: OrderedFloat<f32>,
}
impl<T> ContextPair<T> {
pub fn transform<F, U>(self, mut f: F) -> OperationResult<ContextPair<U>>
where
F: FnMut(T) -> OperationResult<U>,
{
Ok(ContextPair {
positive: f(self.positive)?,
negative: f(self.negative)?,
partial_computation: self.partial_computation,
})
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize)]
pub struct NaiveFeedbackCoefficients {
pub a: OrderedFloat<f32>,
pub b: OrderedFloat<f32>,
pub c: OrderedFloat<f32>,
}
impl NaiveFeedbackCoefficients {
fn extract_context_pairs<TVector: Clone>(
&self,
feedback: Vec<FeedbackItem<TVector>>,
margin: f32,
) -> Vec<ContextPair<TVector>> {
if feedback.len() < 2 {
return Vec::new();
}
let mut feedback_pairs = Vec::new();
for permutation in feedback.iter().permutations(2) {
let (positive, negative) = (permutation[0], permutation[1]);
let confidence = positive.score - negative.score;
if confidence.0 <= margin {
continue;
}
let partial_computation = confidence.powf(self.b.0) * self.c.0;
feedback_pairs.push(ContextPair {
positive: positive.vector.clone(),
negative: negative.vector.clone(),
partial_computation: partial_computation.into(),
});
}
feedback_pairs
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Hash)]
pub struct FeedbackQuery<TVector> {
target: TVector,
context_pairs: Vec<ContextPair<TVector>>,
coefficients: NaiveFeedbackCoefficients,
}
impl<TVector: Clone> FeedbackQuery<TVector> {
pub fn new(
target: TVector,
feedback: Vec<FeedbackItem<TVector>>,
coefficients: NaiveFeedbackCoefficients,
) -> Self {
let context_pairs = coefficients.extract_context_pairs(feedback, 0.0);
Self {
target,
context_pairs,
coefficients,
}
}
}
impl<T, U> TransformInto<FeedbackQuery<U>, T, U> for FeedbackQuery<T> {
fn transform<F>(self, mut f: F) -> OperationResult<FeedbackQuery<U>>
where
F: FnMut(T) -> OperationResult<U>,
{
let Self {
target,
context_pairs,
coefficients,
} = self;
Ok(FeedbackQuery {
target: f(target)?,
context_pairs: context_pairs
.into_iter()
.map(|pair| pair.transform(&mut f))
.try_collect()?,
coefficients,
})
}
}
impl<T> Query<T> for FeedbackQuery<T> {
fn score_by(&self, similarity: impl Fn(&T) -> ScoreType) -> ScoreType {
let Self {
target,
context_pairs,
coefficients,
} = self;
let mut score = coefficients.a.0 * similarity(target);
for pair in context_pairs {
let ContextPair {
positive,
negative,
partial_computation,
} = pair;
let delta = similarity(positive) - similarity(negative);
score += partial_computation.0 * delta;
}
score
}
}