use crate::core::config::DistanceKind;
use crate::image::recurrence_plot::{RecurrencePlot, RecurrencePlotConfig};
#[derive(Debug, Clone)]
pub struct JointRecurrencePlotConfig {
pub dimension: usize,
pub time_delay: usize,
pub threshold: Option<f64>,
pub percentage: Option<f64>,
pub distance: DistanceKind,
}
impl JointRecurrencePlotConfig {
pub fn new() -> Self {
Self {
dimension: 1,
time_delay: 1,
threshold: None,
percentage: None,
distance: DistanceKind::Squared,
}
}
}
impl Default for JointRecurrencePlotConfig {
fn default() -> Self {
Self::new()
}
}
pub struct JointRecurrencePlot;
impl JointRecurrencePlot {
pub fn transform(
config: &JointRecurrencePlotConfig,
x: &[Vec<Vec<f64>>],
) -> Vec<Vec<Vec<f64>>> {
assert!(!x.is_empty(), "Input must have at least one sample");
let n_features = x[0].len();
assert!(n_features >= 1, "Must have at least one feature");
let rp_config = RecurrencePlotConfig {
dimension: config.dimension,
time_delay: config.time_delay,
threshold: config.threshold,
percentage: config.percentage,
distance: config.distance,
};
x.iter()
.map(|sample| {
let mut joint_rp: Option<Vec<Vec<f64>>> = None;
for feat in sample {
let rps = RecurrencePlot::transform(&rp_config, std::slice::from_ref(feat));
let rp = &rps[0];
joint_rp = Some(match joint_rp {
None => rp.clone(),
Some(existing) => {
existing
.iter()
.zip(rp.iter())
.map(|(row_a, row_b)| {
row_a
.iter()
.zip(row_b.iter())
.map(|(&a, &b)| a * b)
.collect()
})
.collect()
}
});
}
joint_rp.unwrap()
})
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_jrp_basic() {
let config = JointRecurrencePlotConfig::new();
let x = vec![vec![
vec![0.0, 1.0, 0.0, -1.0, 0.0],
vec![1.0, 0.0, -1.0, 0.0, 1.0],
]];
let result = JointRecurrencePlot::transform(&config, &x);
assert_eq!(result.len(), 1);
assert_eq!(result[0].len(), 5);
assert_eq!(result[0][0].len(), 5);
}
#[test]
fn test_jrp_symmetric() {
let config = JointRecurrencePlotConfig::new();
let x = vec![vec![vec![1.0, 2.0, 3.0, 4.0], vec![4.0, 3.0, 2.0, 1.0]]];
let result = JointRecurrencePlot::transform(&config, &x);
for i in 0..4 {
for j in 0..4 {
assert!(
(result[0][i][j] - result[0][j][i]).abs() < 1e-10,
"JRP should be symmetric"
);
}
}
}
#[test]
fn test_jrp_with_threshold() {
let config = JointRecurrencePlotConfig {
threshold: Some(1.0),
..JointRecurrencePlotConfig::new()
};
let x = vec![vec![vec![0.0, 0.5, 0.0, 2.0], vec![0.0, 0.5, 0.0, 2.0]]];
let result = JointRecurrencePlot::transform(&config, &x);
for row in &result[0] {
for &v in row {
assert!(v == 0.0 || v == 1.0, "Binary JRP should be 0 or 1");
}
}
}
}