fast algorithm
This commit is contained in:
parent
486b040a64
commit
74a276216d
1 changed files with 120 additions and 150 deletions
270
src/main.rs
270
src/main.rs
|
|
@ -4,20 +4,23 @@ use std::io::{self, BufRead, BufReader};
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct Point {
|
struct Point {
|
||||||
value: u32,
|
value: u32,
|
||||||
cluster_id: Option<usize>,
|
|
||||||
z_score: Option<f32>, // Added field for Z-score
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Point {
|
impl Point {
|
||||||
fn new(value: u32) -> Self {
|
fn new(value: u32) -> Self {
|
||||||
Point {
|
Point { value }
|
||||||
value,
|
|
||||||
cluster_id: None,
|
|
||||||
z_score: None, // Initialize Z-score as None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct ClusterGapInfo {
|
||||||
|
span_length: f32, // Full span length
|
||||||
|
num_elements: usize, // Number of elements, 0 for gaps
|
||||||
|
centroid: f32, // Centroid value
|
||||||
|
z_score: Option<f32>, // Z-score, to be calculated later
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fn load_dataset(filename: &str) -> io::Result<Vec<Point>> {
|
fn load_dataset(filename: &str) -> io::Result<Vec<Point>> {
|
||||||
let file = File::open(filename)?;
|
let file = File::open(filename)?;
|
||||||
let reader = BufReader::new(file);
|
let reader = BufReader::new(file);
|
||||||
|
|
@ -32,168 +35,135 @@ fn load_dataset(filename: &str) -> io::Result<Vec<Point>> {
|
||||||
Ok(dataset)
|
Ok(dataset)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calculate_mean_distance(dataset: &[Point]) -> f32 {
|
// Define the ClusterGapInfo struct as described above
|
||||||
if dataset.len() < 2 { return 0.0; }
|
|
||||||
let total_distance: f32 = dataset.windows(2)
|
fn calculate_densities_and_gaps(dataset: &[Point], factor: f32, min_cluster_size: usize) -> Vec<ClusterGapInfo> {
|
||||||
.map(|w| distance(&w[0], &w[1]) as f32)
|
let mut results: Vec<ClusterGapInfo> = Vec::new();
|
||||||
.sum();
|
if dataset.len() < 2 {
|
||||||
total_distance / (dataset.len() - 1) as f32
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mean_distance = dataset.windows(2)
|
||||||
|
.map(|w| distance(&w[0], &w[1]) as f32)
|
||||||
|
.sum::<f32>() / (dataset.len() - 1) as f32;
|
||||||
|
|
||||||
|
let cluster_threshold = 1.0 / factor * mean_distance;
|
||||||
|
let gap_threshold = factor * mean_distance * 2.0;
|
||||||
|
|
||||||
|
let mut current_cluster = Vec::new();
|
||||||
|
for window in dataset.windows(2) {
|
||||||
|
let gap_distance = distance(&window[0], &window[1]) as f32;
|
||||||
|
if gap_distance <= cluster_threshold {
|
||||||
|
current_cluster.push(window[1].clone());
|
||||||
|
} else {
|
||||||
|
// Before clearing the current_cluster, check if it meets the size requirement
|
||||||
|
if !current_cluster.is_empty() && current_cluster.len() >= min_cluster_size {
|
||||||
|
let cluster_info = create_cluster_info(¤t_cluster);
|
||||||
|
results.push(cluster_info);
|
||||||
|
}
|
||||||
|
current_cluster.clear();
|
||||||
|
|
||||||
|
// Add a gap if the distance exceeds the gap threshold
|
||||||
|
if gap_distance > gap_threshold {
|
||||||
|
results.push(ClusterGapInfo {
|
||||||
|
span_length: gap_distance,
|
||||||
|
num_elements: 0,
|
||||||
|
centroid: (window[0].value as f32 + window[1].value as f32) / 2.0,
|
||||||
|
z_score: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the last cluster if it meets the size requirement
|
||||||
|
if !current_cluster.is_empty() && current_cluster.len() >= min_cluster_size {
|
||||||
|
let cluster_info = create_cluster_info(¤t_cluster);
|
||||||
|
results.push(cluster_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
results
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Additional helper function to create cluster information
|
||||||
|
fn create_cluster_info(cluster: &[Point]) -> ClusterGapInfo {
|
||||||
|
let num_elements = cluster.len();
|
||||||
|
let span_length = (cluster.last().unwrap().value as f32) - (cluster.first().unwrap().value as f32);
|
||||||
|
let centroid = cluster.iter().map(|p| p.value as f32).sum::<f32>() / num_elements as f32;
|
||||||
|
|
||||||
|
ClusterGapInfo {
|
||||||
|
span_length,
|
||||||
|
num_elements,
|
||||||
|
centroid,
|
||||||
|
z_score: None, // Placeholder, to be calculated later
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust the main function and subsequent calculations to work with the new structure and calculate Z-scores accordingly
|
||||||
|
|
||||||
|
|
||||||
|
fn calculate_cluster_density(cluster: &[u32]) -> f32 {
|
||||||
|
if cluster.len() < 2 { return 0.0; } // Adjust based on how you define density for single-element clusters
|
||||||
|
|
||||||
|
let &max_value = cluster.iter().max().unwrap();
|
||||||
|
let &min_value = cluster.iter().min().unwrap();
|
||||||
|
let span = (max_value - min_value) as f32;
|
||||||
|
if span == 0.0 {
|
||||||
|
return cluster.len() as f32; // Handle clusters where all points have the same value
|
||||||
|
}
|
||||||
|
cluster.len() as f32 / span
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume distance function is defined as before
|
||||||
|
|
||||||
|
|
||||||
fn distance(p1: &Point, p2: &Point) -> u32 {
|
fn distance(p1: &Point, p2: &Point) -> u32 {
|
||||||
if p1.value > p2.value {
|
if p1.value > p2.value { p1.value - p2.value } else { p2.value - p1.value }
|
||||||
p1.value.wrapping_sub(p2.value)
|
|
||||||
} else {
|
|
||||||
p2.value.wrapping_sub(p1.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gap_distances(dataset: &[Point]) -> Vec<f32> {
|
|
||||||
dataset.windows(2)
|
|
||||||
.map(|pair| distance(&pair[0], &pair[1]) as f32)
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mean(values: &[f32]) -> f32 {
|
|
||||||
values.iter().sum::<f32>() / values.len() as f32
|
|
||||||
}
|
|
||||||
|
|
||||||
fn std_dev(values: &[f32], mean: f32) -> f32 {
|
|
||||||
let variance = values.iter().map(|&v| (v - mean).powi(2)).sum::<f32>() / values.len() as f32;
|
|
||||||
variance.sqrt()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mark_voids(dataset: &[Point], mean_distance: f32, factor: f32, z_score_threshold: f32, std_dev_distance: f32) -> Vec<(f32, f32)> {
|
|
||||||
let significant_gap_distance = 2.0 * factor * mean_distance;
|
|
||||||
let mut voids = Vec::new();
|
|
||||||
|
|
||||||
for i in 0..dataset.len() - 1 {
|
|
||||||
let gap_distance = distance(&dataset[i], &dataset[i + 1]) as f32;
|
|
||||||
if gap_distance >= significant_gap_distance {
|
|
||||||
// Calculate the midpoint of the gap
|
|
||||||
let midpoint = (dataset[i].value as f32 + dataset[i + 1].value as f32) / 2.0;
|
|
||||||
|
|
||||||
// Calculate Z-score for the gap based on its deviation from the mean distance
|
|
||||||
let z_score = (gap_distance - mean_distance) / std_dev_distance;
|
|
||||||
|
|
||||||
if z_score > z_score_threshold {
|
|
||||||
voids.push((midpoint, z_score));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
voids
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn expand_cluster(dataset: &mut [Point], core_index: usize, cluster_id: usize, max_distance: f32) {
|
fn calculate_mean_and_std_dev(densities: &[f32]) -> (f32, f32) {
|
||||||
let mut indices_to_visit = vec![core_index];
|
let mean = densities.iter().sum::<f32>() / densities.len() as f32;
|
||||||
while let Some(current_index) = indices_to_visit.pop() {
|
let variance = densities.iter().map(|&v| (v - mean).powi(2)).sum::<f32>() / densities.len() as f32;
|
||||||
if dataset[current_index].cluster_id.is_none() {
|
(mean, variance.sqrt())
|
||||||
dataset[current_index].cluster_id = Some(cluster_id);
|
|
||||||
|
|
||||||
let new_neighbors: Vec<usize> = dataset.iter().enumerate()
|
|
||||||
.filter(|&(idx, other_point)| {
|
|
||||||
other_point.cluster_id.is_none() &&
|
|
||||||
distance(&dataset[current_index], other_point) as f32 <= max_distance
|
|
||||||
})
|
|
||||||
.map(|(idx, _)| idx)
|
|
||||||
.filter(|&idx| !indices_to_visit.contains(&idx))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
indices_to_visit.extend(new_neighbors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dbscan(dataset: &mut [Point], min_cluster_size: usize, factor: f32, z_score_threshold: f32) {
|
fn calculate_z_scores(densities: &[f32], mean: f32, std_dev: f32) -> Vec<f32> {
|
||||||
let gaps = gap_distances(dataset); // Calculate gap distances
|
densities.iter().map(|&density| (density - mean) / std_dev).collect()
|
||||||
let mean_gap_distance = mean(&gaps); // Calculate the mean of gap distances
|
|
||||||
let std_dev_gap_distance = std_dev(&gaps, mean_gap_distance); // Calculate std dev of gap distances
|
|
||||||
let max_distance = mean_gap_distance * (1.0 / factor);
|
|
||||||
|
|
||||||
// Adjusted call to mark_voids to use std_dev_gap_distance
|
|
||||||
let voids = mark_voids(dataset, mean_gap_distance, factor, z_score_threshold, std_dev_gap_distance);
|
|
||||||
|
|
||||||
// Now you can use the voids information
|
|
||||||
for (midpoint, z_score) in voids {
|
|
||||||
println!("void Midpoint: {}, Z-Score: {}", midpoint, z_score);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut cluster_id = 0;
|
|
||||||
for idx in 0..dataset.len() {
|
|
||||||
if dataset[idx].cluster_id.is_none() && dataset[idx].z_score.is_none() {
|
|
||||||
let mut neighbors = Vec::new();
|
|
||||||
for (n_idx, other_point) in dataset.iter().enumerate() {
|
|
||||||
if distance(&dataset[idx], other_point) as f32 <= max_distance {
|
|
||||||
neighbors.push(n_idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if neighbors.len() >= min_cluster_size {
|
|
||||||
cluster_id += 1;
|
|
||||||
for &n_idx in &neighbors {
|
|
||||||
dataset[n_idx].cluster_id = Some(cluster_id);
|
|
||||||
}
|
|
||||||
expand_cluster(dataset, idx, cluster_id, max_distance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calculate_centroids_z_scores(dataset: &[Point]) -> Vec<(usize, f32)> {
|
|
||||||
let mean_value = mean(&dataset.iter().map(|p| p.value as f32).collect::<Vec<f32>>());
|
|
||||||
let std_dev_value = std_dev(&dataset.iter().map(|p| p.value as f32).collect::<Vec<f32>>(), mean_value);
|
|
||||||
|
|
||||||
let mut centroids_z_scores: Vec<(usize, f32)> = Vec::new();
|
|
||||||
|
|
||||||
let max_cluster_id = dataset.iter().filter_map(|p| p.cluster_id).max().unwrap_or(0);
|
|
||||||
for cluster_id in 1..=max_cluster_id {
|
|
||||||
let cluster_points: Vec<&Point> = dataset.iter().filter(|p| p.cluster_id == Some(cluster_id)).collect();
|
|
||||||
if cluster_points.is_empty() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let centroid_value = cluster_points.iter().map(|p| p.value as f32).sum::<f32>() / cluster_points.len() as f32;
|
|
||||||
let z_score = (centroid_value - mean_value) / std_dev_value;
|
|
||||||
|
|
||||||
centroids_z_scores.push((cluster_id, z_score));
|
|
||||||
}
|
|
||||||
|
|
||||||
centroids_z_scores
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> io::Result<()> {
|
fn main() -> io::Result<()> {
|
||||||
let filename = "random_values.txt";
|
let filename = "random_values.txt";
|
||||||
let mut dataset = load_dataset(filename)?;
|
let dataset = load_dataset(filename)?;
|
||||||
|
|
||||||
let min_cluster_size = 7; // Adjust as needed
|
let factor = 3.8;
|
||||||
let factor = 1.6; // Adjust as needed
|
let min_cluster_size = 7;
|
||||||
let z_score_threshold = 1.6; // Define your Z-score threshold here
|
|
||||||
|
|
||||||
dbscan(&mut dataset, min_cluster_size, factor, z_score_threshold);
|
let mut cluster_gap_infos = calculate_densities_and_gaps(&dataset, factor, min_cluster_size);
|
||||||
|
|
||||||
// Calculate Z-scores for centroids of dense clusters
|
// Calculate mean distance for Z-score computation
|
||||||
let centroids_z_scores = calculate_centroids_z_scores(&dataset);
|
let total_distances: f32 = dataset.windows(2)
|
||||||
|
.map(|w| (w[1].value as f32 - w[0].value as f32))
|
||||||
|
.sum();
|
||||||
|
let mean_distance = total_distances / (dataset.len() as f32 - 1.0);
|
||||||
|
|
||||||
// Iterate through the dataset to print voids
|
// Calculate Z-scores for clusters and gaps
|
||||||
for point in &dataset {
|
for info in cluster_gap_infos.iter_mut() {
|
||||||
if let Some(z_score) = point.z_score {
|
if info.num_elements == 0 {
|
||||||
if z_score < 0.0 { // Assuming negative Z-scores indicate voids
|
// Z-score for gaps
|
||||||
println!("void: {}, Z-Score: {}", point.value, z_score);
|
info.z_score = Some((info.span_length - mean_distance) / mean_distance); // Simplified deviation measure
|
||||||
}
|
} else {
|
||||||
|
// Z-score for clusters, based on density deviation
|
||||||
|
let density = info.num_elements as f32 / info.span_length;
|
||||||
|
let expected_density = 1.0 / mean_distance; // Expected: one element per mean distance
|
||||||
|
info.z_score = Some((density - expected_density) / expected_density); // Simplified deviation measure
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print information for dense cluster centroids
|
// Output results with distinction between clusters and gaps
|
||||||
for (cluster_id, z_score) in centroids_z_scores {
|
for (index, info) in cluster_gap_infos.iter().enumerate() {
|
||||||
let cluster_points: Vec<&Point> = dataset.iter().filter(|p| p.cluster_id == Some(cluster_id)).collect();
|
let element_type = if info.num_elements > 0 { "Cluster" } else { "Gap" };
|
||||||
if !cluster_points.is_empty() {
|
println!("Num Elements: {}, Centroid: {:.2}, Z-Score: {:.2}, Span: {:.2}, ",
|
||||||
// Find the value closest to the centroid
|
info.num_elements, info.centroid, info.z_score.unwrap_or(0.0), info.span_length);
|
||||||
let centroid_value = cluster_points.iter().map(|p| p.value as f32).sum::<f32>() / cluster_points.len() as f32;
|
|
||||||
let closest_point = cluster_points.iter().min_by_key(|&&p| ((p.value as f32 - centroid_value).abs() * 1000.0) as u32).unwrap();
|
|
||||||
println!("Attractor: {}, Z-Score: {}", closest_point.value, z_score);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue