use crate::polyglot::BookEntry;
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum MergeStrategy {
AvgWeight,
MaxWeight,
MinWeight,
Ours,
PercentageAverage,
SumWeight,
WeightedAverageWeight(f64, f64),
WeightedDistance,
WeightedMedian,
DynamicMidpoint,
Entropy,
GeometricScaling,
HarmonicMean,
LogarithmicAverage,
QuadraticMean,
Sigmoid,
Sort,
}
pub fn average_weight(entry1: &BookEntry, entry2: &BookEntry) -> u16 {
let entry1_weight: u32 = entry1.weight.into();
let entry2_weight: u32 = entry2.weight.into();
let sum = entry1_weight.saturating_add(entry2_weight);
(sum / 2).try_into().unwrap_or(u16::MAX)
}
pub fn weighted_average_weight(
entry1: &BookEntry,
entry2: &BookEntry,
book1_weight: f64,
book2_weight: f64,
) -> u16 {
let total_weight = book1_weight + book2_weight;
let normalized_book1_weight = book1_weight / total_weight;
let normalized_book2_weight = book2_weight / total_weight;
let entry1_weight: f64 = entry1.weight.into();
let entry2_weight: f64 = entry2.weight.into();
let weighted_sum =
entry1_weight * normalized_book1_weight + entry2_weight * normalized_book2_weight;
weighted_sum.min(f64::from(u16::MAX)).round() as u16
}
fn sigmoid(x: f64) -> f64 {
1.0 / (1.0 + (-x).exp())
}
fn inverse_sigmoid(y: f64) -> f64 {
(y / (1.0 - y)).ln()
}
pub fn sigmoid_merge(a: u16, b: u16) -> u16 {
let a_norm = sigmoid(f64::from(a) - 32768.0);
let b_norm = sigmoid(f64::from(b) - 32768.0);
let m_norm = (a_norm + b_norm) / 2.0;
let merged = 32768.0 + inverse_sigmoid(m_norm);
merged.round() as u16
}
pub fn harmonic_merge(a: u16, b: u16) -> u16 {
if a == 0 {
return b;
}
if b == 0 {
return a;
}
let merged = 2.0 / (1.0 / f64::from(a) + 1.0 / f64::from(b));
merged.round() as u16
}
pub fn geometric_scaling_merge(a: u16, b: u16) -> u16 {
let a_scaled = (f64::from(a) / f64::from(u16::MAX)) * f64::from(u32::MAX);
let b_scaled = (f64::from(b) / f64::from(u16::MAX)) * f64::from(u32::MAX);
let m_scaled = (a_scaled * b_scaled).sqrt();
let merged = (m_scaled / f64::from(u32::MAX)) * f64::from(u16::MAX);
merged.round() as u16
}
pub fn logarithmic_merge(a: u16, b: u16) -> u16 {
let log_a = (f64::from(a) + 1.0).log2();
let log_b = (f64::from(b) + 1.0).log2();
let avg_log = (log_a + log_b) / 2.0;
let merged_weight = 2.0f64.powf(avg_log) - 1.0;
merged_weight.min(f64::from(u16::MAX)).round() as u16
}
pub fn quadratic_mean_merge(a: u16, b: u16) -> u16 {
let squared_sum = (f64::from(a)).powi(2) + (f64::from(b)).powi(2);
let quadratic_mean = (squared_sum / 2.0).sqrt();
quadratic_mean.min(f64::from(u16::MAX)).round() as u16
}
fn entropy(p: f64) -> f64 {
if p == 0.0 || p == 1.0 {
return 0.0;
}
-p * p.log2()
}
pub fn entropy_merge(a: u16, b: u16) -> u16 {
let p_a = f64::from(a) / f64::from(u16::MAX);
let p_b = f64::from(b) / f64::from(u16::MAX);
let entropy_a = entropy(p_a);
let entropy_b = entropy(p_b);
let avg_entropy = (entropy_a + entropy_b) / 2.0;
let merged_weight = avg_entropy * f64::from(u16::MAX);
merged_weight.min(f64::from(u16::MAX)).round() as u16
}
pub fn weighted_median_merge(a: u16, b: u16) -> u16 {
let mut sorted_weights = [a, b];
sorted_weights.sort();
let median = (f64::from(sorted_weights[0]) + f64::from(sorted_weights[1])) / 2.0;
let distance_a = (f64::from(a) - median).abs();
let distance_b = (f64::from(b) - median).abs();
let weighted_median =
(f64::from(a) * distance_a + f64::from(b) * distance_b) / (distance_a + distance_b);
weighted_median.round() as u16
}
pub fn weighted_distance_merge(a: u16, b: u16) -> u16 {
let norm_a = f64::from(a) / f64::from(u16::MAX);
let norm_b = f64::from(b) / f64::from(u16::MAX);
let distance_a = 1.0 - norm_a;
let distance_b = 1.0 - norm_b;
let merged_weight = (norm_a * distance_b + norm_b * distance_a) / (distance_a + distance_b);
(merged_weight * f64::from(u16::MAX)).round() as u16
}
pub fn dynamic_midpoint_merge(a: u16, b: u16) -> u16 {
let difference = f64::from((i32::from(a) - i32::from(b)).abs());
let dynamic_factor = difference / f64::from(u16::MAX);
let merged_weight = (f64::from(a.min(b))) + dynamic_factor * difference;
merged_weight.min(f64::from(u16::MAX)).round() as u16
}