use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SoarConfig {
pub num_secondary: usize,
pub selective: bool,
pub spill_threshold: f32,
}
impl Default for SoarConfig {
fn default() -> Self {
Self {
num_secondary: 1,
selective: true,
spill_threshold: 0.5,
}
}
}
impl SoarConfig {
pub fn new() -> Self {
Self::default()
}
pub fn with_secondary(num_secondary: usize) -> Self {
Self {
num_secondary,
..Default::default()
}
}
pub fn selective(mut self, enabled: bool) -> Self {
self.selective = enabled;
self
}
pub fn threshold(mut self, threshold: f32) -> Self {
self.spill_threshold = threshold;
self
}
pub fn full() -> Self {
Self {
num_secondary: 1,
selective: false,
spill_threshold: 0.0,
}
}
pub fn aggressive() -> Self {
Self {
num_secondary: 2,
selective: false,
spill_threshold: 0.0,
}
}
}
#[derive(Debug, Clone)]
pub struct MultiAssignment {
pub primary_cluster: u32,
pub secondary_clusters: Vec<u32>,
}
impl MultiAssignment {
pub fn primary_only(cluster: u32) -> Self {
Self {
primary_cluster: cluster,
secondary_clusters: Vec::new(),
}
}
pub fn all_clusters(&self) -> impl Iterator<Item = u32> + '_ {
std::iter::once(self.primary_cluster).chain(self.secondary_clusters.iter().copied())
}
pub fn num_assignments(&self) -> usize {
1 + self.secondary_clusters.len()
}
pub fn is_spilled(&self) -> bool {
!self.secondary_clusters.is_empty()
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub struct SoarStats {
pub total_vectors: usize,
pub spilled_vectors: usize,
pub total_assignments: usize,
}
#[allow(dead_code)]
impl SoarStats {
pub fn new() -> Self {
Self::default()
}
pub fn record(&mut self, assignment: &MultiAssignment) {
self.total_vectors += 1;
self.total_assignments += assignment.num_assignments();
if assignment.is_spilled() {
self.spilled_vectors += 1;
}
}
pub fn spill_ratio(&self) -> f32 {
if self.total_vectors == 0 {
0.0
} else {
self.spilled_vectors as f32 / self.total_vectors as f32
}
}
pub fn avg_assignments(&self) -> f32 {
if self.total_vectors == 0 {
0.0
} else {
self.total_assignments as f32 / self.total_vectors as f32
}
}
pub fn storage_factor(&self) -> f32 {
self.avg_assignments()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_soar_config_default() {
let config = SoarConfig::default();
assert_eq!(config.num_secondary, 1);
assert!(config.selective);
}
#[test]
fn test_multi_assignment() {
let assignment = MultiAssignment {
primary_cluster: 5,
secondary_clusters: vec![2, 7],
};
assert_eq!(assignment.num_assignments(), 3);
assert!(assignment.is_spilled());
let all: Vec<u32> = assignment.all_clusters().collect();
assert_eq!(all, vec![5, 2, 7]);
}
#[test]
fn test_soar_stats() {
let mut stats = SoarStats::new();
stats.record(&MultiAssignment::primary_only(0));
stats.record(&MultiAssignment {
primary_cluster: 1,
secondary_clusters: vec![2],
});
assert_eq!(stats.total_vectors, 2);
assert_eq!(stats.spilled_vectors, 1);
assert_eq!(stats.total_assignments, 3);
assert_eq!(stats.spill_ratio(), 0.5);
assert_eq!(stats.avg_assignments(), 1.5);
}
}