use super::encoding::QuantizationConfig;
use crate::transducer::{Algorithm, Transducer};
use libdictenstein::dynamic_dawg::DynamicDawg;
use libdictenstein::DictionaryValue;
use std::collections::HashMap;
#[derive(Debug)]
pub struct TimeSeriesIndex<V: DictionaryValue = usize> {
dawg: DynamicDawg<V>,
config: QuantizationConfig,
originals: HashMap<V, Vec<f64>>,
store_originals: bool,
count: usize,
}
impl<V: DictionaryValue + std::hash::Hash + Eq + Copy> TimeSeriesIndex<V> {
pub fn new(config: QuantizationConfig) -> Self {
Self {
dawg: DynamicDawg::new(),
config,
originals: HashMap::new(),
store_originals: false,
count: 0,
}
}
pub fn new_with_verification(config: QuantizationConfig) -> Self {
Self {
dawg: DynamicDawg::new(),
config,
originals: HashMap::new(),
store_originals: true,
count: 0,
}
}
#[inline]
pub fn config(&self) -> &QuantizationConfig {
&self.config
}
#[inline]
pub fn len(&self) -> usize {
self.count
}
#[inline]
pub fn is_empty(&self) -> bool {
self.count == 0
}
pub fn insert(&mut self, value: V, series: &[f64]) -> bool {
let encoded = self.config.encode_u8(series);
let inserted = self.dawg.insert_bytes_with_value(&encoded, value);
if inserted {
self.count += 1;
if self.store_originals {
self.originals.insert(value, series.to_vec());
}
}
inserted
}
pub fn contains(&self, series: &[f64]) -> bool {
let encoded = self.config.encode_u8(series);
self.dawg.contains_bytes(&encoded)
}
pub fn get(&self, series: &[f64]) -> Option<V> {
let encoded = self.config.encode_u8(series);
self.dawg.get_bytes_value(&encoded)
}
pub fn search(&self, query: &[f64], max_distance: usize) -> Vec<(V, usize)> {
self.search_with_algorithm(query, max_distance, Algorithm::Standard)
}
pub fn search_transposition(&self, query: &[f64], max_distance: usize) -> Vec<(V, usize)> {
self.search_with_algorithm(query, max_distance, Algorithm::Transposition)
}
pub fn search_merge_split(&self, query: &[f64], max_distance: usize) -> Vec<(V, usize)> {
self.search_with_algorithm(query, max_distance, Algorithm::MergeAndSplit)
}
fn search_with_algorithm(
&self,
query: &[f64],
max_distance: usize,
algorithm: Algorithm,
) -> Vec<(V, usize)> {
let encoded = self.config.encode_u8(query);
let query_str = unsafe { std::str::from_utf8_unchecked(&encoded) };
let transducer = Transducer::new(self.dawg.clone(), algorithm);
transducer
.query_candidates(query_str, max_distance)
.filter_map(|candidate| {
self.dawg
.get_bytes_value(candidate.term.as_bytes())
.map(|v| (v, candidate.distance))
})
.collect()
}
pub fn get_candidates_for_verification(
&self,
query: &[f64],
max_distance: usize,
) -> Vec<(V, &[f64])> {
if !self.store_originals {
return Vec::new();
}
let candidates = self.search(query, max_distance);
candidates
.into_iter()
.filter_map(|(value, _)| {
self.originals
.get(&value)
.map(|series| (value, series.as_slice()))
})
.collect()
}
pub fn get_original(&self, value: &V) -> Option<&[f64]> {
self.originals.get(value).map(|v| v.as_slice())
}
pub fn stats(&self) -> TimeSeriesIndexStats {
TimeSeriesIndexStats {
num_series: self.count,
dawg_node_count: self.dawg.node_count(),
stores_originals: self.store_originals,
num_bins: self.config.num_bins,
value_range: (self.config.min_value, self.config.max_value),
}
}
}
impl TimeSeriesIndex<usize> {
pub fn from_series(config: QuantizationConfig, series_list: &[Vec<f64>]) -> Self {
let mut index = Self::new(config);
for (id, series) in series_list.iter().enumerate() {
index.insert(id, series);
}
index
}
pub fn from_series_with_verification(
config: QuantizationConfig,
series_list: &[Vec<f64>],
) -> Self {
let mut index = Self::new_with_verification(config);
for (id, series) in series_list.iter().enumerate() {
index.insert(id, series);
}
index
}
}
#[derive(Debug, Clone)]
pub struct TimeSeriesIndexStats {
pub num_series: usize,
pub dawg_node_count: usize,
pub stores_originals: bool,
pub num_bins: u32,
pub value_range: (f64, f64),
}
impl std::fmt::Display for TimeSeriesIndexStats {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "TimeSeriesIndex Statistics:")?;
writeln!(f, " Series count: {}", self.num_series)?;
writeln!(f, " DAWG nodes: {}", self.dawg_node_count)?;
writeln!(f, " Stores originals: {}", self.stores_originals)?;
writeln!(f, " Quantization bins: {}", self.num_bins)?;
writeln!(
f,
" Value range: [{:.2}, {:.2}]",
self.value_range.0, self.value_range.1
)
}
}
#[derive(Debug, Clone)]
pub struct TimeSeriesIndexBuilder {
config: Option<QuantizationConfig>,
store_originals: bool,
}
impl TimeSeriesIndexBuilder {
pub fn new() -> Self {
Self {
config: None,
store_originals: false,
}
}
pub fn config(mut self, config: QuantizationConfig) -> Self {
self.config = Some(config);
self
}
pub fn quantization(mut self, min: f64, max: f64, bins: u32) -> Self {
self.config = Some(QuantizationConfig::uniform(min, max, bins));
self
}
pub fn with_verification(mut self) -> Self {
self.store_originals = true;
self
}
pub fn auto_config(mut self, sample_data: &[f64], bins: u32, margin: f64) -> Self {
self.config = QuantizationConfig::from_data(sample_data, bins, margin);
self
}
pub fn build<V: DictionaryValue + std::hash::Hash + Eq + Copy>(self) -> TimeSeriesIndex<V> {
let config = self
.config
.expect("Quantization config must be set before building");
if self.store_originals {
TimeSeriesIndex::new_with_verification(config)
} else {
TimeSeriesIndex::new(config)
}
}
}
impl Default for TimeSeriesIndexBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_index_creation() {
let config = QuantizationConfig::for_u8(0.0, 100.0);
let index: TimeSeriesIndex<usize> = TimeSeriesIndex::new(config);
assert!(index.is_empty());
assert_eq!(index.len(), 0);
}
#[test]
fn test_insert_and_contains() {
let config = QuantizationConfig::for_u8(0.0, 100.0);
let mut index = TimeSeriesIndex::new(config);
let series = vec![10.0, 20.0, 30.0];
assert!(index.insert(0usize, &series));
assert!(!index.is_empty());
assert_eq!(index.len(), 1);
assert!(index.contains(&series));
assert!(index.contains(&[10.0, 20.0, 30.0]));
}
#[test]
fn test_insert_duplicate() {
let config = QuantizationConfig::for_u8(0.0, 100.0);
let mut index = TimeSeriesIndex::new(config);
let series = vec![10.0, 20.0, 30.0];
assert!(index.insert(0usize, &series));
assert!(!index.insert(1usize, &series));
assert_eq!(index.len(), 1);
let different_series = vec![50.0, 60.0, 70.0];
assert!(index.insert(2usize, &different_series));
assert_eq!(index.len(), 2);
}
#[test]
fn test_get() {
let config = QuantizationConfig::for_u8(0.0, 100.0);
let mut index = TimeSeriesIndex::new(config);
index.insert(42usize, &[10.0, 20.0, 30.0]);
assert_eq!(index.get(&[10.0, 20.0, 30.0]), Some(42));
assert_eq!(index.get(&[99.0, 99.0, 99.0]), None);
}
#[test]
fn test_search() {
let config = QuantizationConfig::for_u8(0.0, 100.0);
let mut index = TimeSeriesIndex::new(config);
index.insert(0usize, &[10.0, 20.0, 30.0]);
index.insert(1usize, &[15.0, 25.0, 35.0]);
index.insert(2usize, &[50.0, 60.0, 70.0]);
let results = index.search(&[12.0, 22.0, 32.0], 10);
let found_ids: Vec<usize> = results.iter().map(|(id, _)| *id).collect();
assert!(found_ids.contains(&0));
assert!(found_ids.contains(&1));
}
#[test]
fn test_search_exact_match() {
let config = QuantizationConfig::for_u8(0.0, 100.0);
let mut index = TimeSeriesIndex::new(config);
index.insert(0usize, &[10.0, 20.0, 30.0]);
let results = index.search(&[10.0, 20.0, 30.0], 0);
assert_eq!(results.len(), 1);
assert_eq!(results[0].0, 0);
assert_eq!(results[0].1, 0);
}
#[test]
fn test_with_verification() {
let config = QuantizationConfig::for_u8(0.0, 100.0);
let mut index = TimeSeriesIndex::new_with_verification(config);
let series1 = vec![10.0, 20.0, 30.0];
let series2 = vec![15.0, 25.0, 35.0];
index.insert(0usize, &series1);
index.insert(1usize, &series2);
assert_eq!(index.get_original(&0), Some(series1.as_slice()));
assert_eq!(index.get_original(&1), Some(series2.as_slice()));
}
#[test]
fn test_get_candidates_for_verification() {
let config = QuantizationConfig::for_u8(0.0, 100.0);
let mut index = TimeSeriesIndex::new_with_verification(config);
index.insert(0usize, &[10.0, 20.0, 30.0]);
index.insert(1usize, &[15.0, 25.0, 35.0]);
let candidates = index.get_candidates_for_verification(&[12.0, 22.0, 32.0], 10);
assert!(!candidates.is_empty());
for (_, series) in &candidates {
assert_eq!(series.len(), 3);
}
}
#[test]
fn test_from_series() {
let config = QuantizationConfig::for_u8(0.0, 100.0);
let series_data = vec![
vec![10.0, 20.0, 30.0],
vec![15.0, 25.0, 35.0],
vec![50.0, 60.0, 70.0],
];
let index = TimeSeriesIndex::from_series(config, &series_data);
assert_eq!(index.len(), 3);
}
#[test]
fn test_stats() {
let config = QuantizationConfig::for_u8(0.0, 100.0);
let mut index = TimeSeriesIndex::new(config);
index.insert(0usize, &[10.0, 20.0, 30.0]);
index.insert(1usize, &[15.0, 25.0, 35.0]);
let stats = index.stats();
assert_eq!(stats.num_series, 2);
assert!(stats.dawg_node_count > 0);
assert!(!stats.stores_originals);
assert_eq!(stats.num_bins, 256);
}
#[test]
fn test_builder() {
let index: TimeSeriesIndex<usize> = TimeSeriesIndexBuilder::new()
.quantization(0.0, 100.0, 256)
.with_verification()
.build();
assert!(index.is_empty());
assert!(index.store_originals);
}
#[test]
fn test_search_transposition() {
let config = QuantizationConfig::for_u8(0.0, 100.0);
let mut index = TimeSeriesIndex::new(config);
index.insert(0usize, &[10.0, 30.0, 20.0]); index.insert(1usize, &[10.0, 20.0, 30.0]);
let results = index.search_transposition(&[10.0, 20.0, 30.0], 2);
assert!(results.len() >= 1);
}
#[test]
fn test_empty_series() {
let config = QuantizationConfig::for_u8(0.0, 100.0);
let mut index = TimeSeriesIndex::new(config);
index.insert(0usize, &[]);
assert_eq!(index.len(), 1);
assert!(index.contains(&[]));
}
#[test]
fn test_single_element_series() {
let config = QuantizationConfig::for_u8(0.0, 100.0);
let mut index = TimeSeriesIndex::new(config);
index.insert(0usize, &[50.0]);
index.insert(1usize, &[55.0]);
assert!(index.contains(&[50.0]));
assert!(index.contains(&[55.0]));
assert_eq!(index.len(), 2);
}
}