Files
wifi-densepose/vendor/ruvector/crates/ruvector-delta-consensus/src/conflict.rs

286 lines
7.7 KiB
Rust

//! Conflict resolution strategies for concurrent deltas
use ruvector_delta_core::{Delta, VectorDelta};
use crate::{ConsensusError, Result};
/// Conflict resolution strategy
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConflictStrategy {
/// Last write wins (by timestamp)
LastWriteWins,
/// First write wins
FirstWriteWins,
/// Merge all deltas
Merge,
/// Custom resolution function
Custom,
}
/// Result of conflict resolution
#[derive(Debug, Clone)]
pub struct MergeResult {
/// The resolved delta
pub delta: VectorDelta,
/// Number of deltas merged
pub merged_count: usize,
/// Whether conflicts were detected
pub had_conflicts: bool,
}
/// Trait for conflict resolution
pub trait ConflictResolver<T> {
/// Resolve conflicts between multiple deltas
fn resolve(&self, deltas: &[&T]) -> Result<T>;
}
/// Last-write-wins resolver
pub struct LastWriteWinsResolver;
impl ConflictResolver<VectorDelta> for LastWriteWinsResolver {
fn resolve(&self, deltas: &[&VectorDelta]) -> Result<VectorDelta> {
if deltas.is_empty() {
return Err(ConsensusError::InvalidOperation(
"No deltas to resolve".into(),
));
}
// Take the last delta (assumed to be sorted by timestamp)
Ok(deltas.last().unwrap().clone().clone())
}
}
/// First-write-wins resolver
pub struct FirstWriteWinsResolver;
impl ConflictResolver<VectorDelta> for FirstWriteWinsResolver {
fn resolve(&self, deltas: &[&VectorDelta]) -> Result<VectorDelta> {
if deltas.is_empty() {
return Err(ConsensusError::InvalidOperation(
"No deltas to resolve".into(),
));
}
// Take the first delta
Ok(deltas.first().unwrap().clone().clone())
}
}
/// Merge resolver - composes all deltas
#[derive(Default)]
pub struct MergeResolver {
/// Weight for averaging (if applicable)
pub averaging: bool,
}
impl MergeResolver {
/// Create new merge resolver
pub fn new() -> Self {
Self { averaging: false }
}
/// Create with averaging enabled
pub fn with_averaging() -> Self {
Self { averaging: true }
}
}
impl ConflictResolver<VectorDelta> for MergeResolver {
fn resolve(&self, deltas: &[&VectorDelta]) -> Result<VectorDelta> {
if deltas.is_empty() {
return Err(ConsensusError::InvalidOperation(
"No deltas to resolve".into(),
));
}
if deltas.len() == 1 {
return Ok(deltas[0].clone().clone());
}
// Compose all deltas
let mut result = deltas[0].clone().clone();
for delta in deltas.iter().skip(1) {
result = result.compose((*delta).clone());
}
// Optionally average the result
if self.averaging {
let scale = 1.0 / deltas.len() as f32;
result = result.scale(scale);
}
Ok(result)
}
}
/// Weighted merge resolver
pub struct WeightedMergeResolver {
/// Default weight for deltas without explicit weight
pub default_weight: f32,
}
impl WeightedMergeResolver {
/// Create new weighted resolver
pub fn new(default_weight: f32) -> Self {
Self { default_weight }
}
}
/// Maximum magnitude resolver - takes delta with largest L2 norm
pub struct MaxMagnitudeResolver;
impl ConflictResolver<VectorDelta> for MaxMagnitudeResolver {
fn resolve(&self, deltas: &[&VectorDelta]) -> Result<VectorDelta> {
if deltas.is_empty() {
return Err(ConsensusError::InvalidOperation(
"No deltas to resolve".into(),
));
}
let max_delta = deltas
.iter()
.max_by(|a, b| {
a.l2_norm()
.partial_cmp(&b.l2_norm())
.unwrap_or(std::cmp::Ordering::Equal)
})
.unwrap();
Ok((*max_delta).clone())
}
}
/// Minimum magnitude resolver - takes delta with smallest L2 norm
pub struct MinMagnitudeResolver;
impl ConflictResolver<VectorDelta> for MinMagnitudeResolver {
fn resolve(&self, deltas: &[&VectorDelta]) -> Result<VectorDelta> {
if deltas.is_empty() {
return Err(ConsensusError::InvalidOperation(
"No deltas to resolve".into(),
));
}
let min_delta = deltas
.iter()
.min_by(|a, b| {
a.l2_norm()
.partial_cmp(&b.l2_norm())
.unwrap_or(std::cmp::Ordering::Equal)
})
.unwrap();
Ok((*min_delta).clone())
}
}
/// Clipped merge resolver - merges and clips to range
pub struct ClippedMergeResolver {
/// Minimum value
pub min: f32,
/// Maximum value
pub max: f32,
}
impl ClippedMergeResolver {
/// Create new clipped resolver
pub fn new(min: f32, max: f32) -> Self {
Self { min, max }
}
}
impl ConflictResolver<VectorDelta> for ClippedMergeResolver {
fn resolve(&self, deltas: &[&VectorDelta]) -> Result<VectorDelta> {
let merge = MergeResolver::new();
let merged = merge.resolve(deltas)?;
Ok(merged.clip(self.min, self.max))
}
}
/// Resolve by sparsity - prefer sparser deltas
pub struct SparsityResolver;
impl ConflictResolver<VectorDelta> for SparsityResolver {
fn resolve(&self, deltas: &[&VectorDelta]) -> Result<VectorDelta> {
if deltas.is_empty() {
return Err(ConsensusError::InvalidOperation(
"No deltas to resolve".into(),
));
}
// Take the sparsest delta
let sparsest = deltas.iter().min_by_key(|d| d.value.nnz()).unwrap();
Ok((*sparsest).clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_last_write_wins() {
let d1 = VectorDelta::from_dense(vec![1.0, 0.0, 0.0]);
let d2 = VectorDelta::from_dense(vec![0.0, 1.0, 0.0]);
let d3 = VectorDelta::from_dense(vec![0.0, 0.0, 1.0]);
let resolver = LastWriteWinsResolver;
let result = resolver.resolve(&[&d1, &d2, &d3]).unwrap();
// Should return d3 (last)
assert_eq!(result.dimensions, 3);
}
#[test]
fn test_first_write_wins() {
let d1 = VectorDelta::from_dense(vec![1.0, 0.0, 0.0]);
let d2 = VectorDelta::from_dense(vec![0.0, 1.0, 0.0]);
let resolver = FirstWriteWinsResolver;
let result = resolver.resolve(&[&d1, &d2]).unwrap();
// Should return d1 (first)
assert_eq!(result.dimensions, 3);
}
#[test]
fn test_merge_resolver() {
let d1 = VectorDelta::from_dense(vec![1.0, 0.0, 0.0]);
let d2 = VectorDelta::from_dense(vec![0.0, 1.0, 0.0]);
let resolver = MergeResolver::new();
let result = resolver.resolve(&[&d1, &d2]).unwrap();
// Should compose both deltas
assert!(!result.is_identity());
}
#[test]
fn test_max_magnitude() {
let small = VectorDelta::from_dense(vec![0.1, 0.1, 0.1]);
let large = VectorDelta::from_dense(vec![1.0, 1.0, 1.0]);
let resolver = MaxMagnitudeResolver;
let result = resolver.resolve(&[&small, &large]).unwrap();
// Should return the larger delta
assert!(result.l2_norm() > 1.0);
}
#[test]
fn test_clipped_merge() {
let d1 = VectorDelta::from_dense(vec![10.0, -10.0]);
let d2 = VectorDelta::from_dense(vec![5.0, -5.0]);
let resolver = ClippedMergeResolver::new(-1.0, 1.0);
let result = resolver.resolve(&[&d1, &d2]).unwrap();
// Values should be clipped to [-1, 1]
if let ruvector_delta_core::DeltaValue::Dense(values) = &result.value {
assert!(values[0] <= 1.0);
assert!(values[1] >= -1.0);
}
}
}