use crate::reconstruction::{Cluster, ClusteringResult};
use crate::SpacePoint;
use alpha_g_detector::alpha16::aw_map::INNER_CATHODE_RADIUS;
use indexmap::IndexMap;
use uom::si::f64::{Angle, Length, ReciprocalLength};
use uom::si::ratio::ratio;
use uom::typenum::P2;
pub(crate) fn cluster_spacepoints(
mut sp: Vec<SpacePoint>,
min_num_points_per_cluster: usize,
rho_bins: u32,
theta_bins: u32,
max_distance: Length,
) -> ClusteringResult {
let mut accumulator = HoughSpaceAccumulator {
rho_bins,
theta_bins,
accumulator: IndexMap::new(),
};
for &point in sp.iter() {
accumulator.add(point);
}
fn best_cluster(
accumulator: &mut HoughSpaceAccumulator,
max_distance: Length,
) -> Vec<SpacePoint> {
let mut prev_best = Vec::new();
loop {
let best = largest_cluster(accumulator.most_popular(), max_distance);
if best.len() <= prev_best.len() {
break;
}
for &point in best.iter() {
accumulator.remove_unchecked(point);
}
for &point in prev_best.iter() {
accumulator.add(point);
}
prev_best = best;
}
prev_best
}
let mut clusters = Vec::new();
loop {
let cluster = best_cluster(&mut accumulator, max_distance);
if cluster.len() < min_num_points_per_cluster {
break;
}
clusters.push(Cluster(cluster));
}
for &point in clusters.iter().flatten() {
let index = sp.iter().position(|&p| p == point).unwrap();
sp.swap_remove(index);
}
ClusteringResult {
clusters,
remainder: sp,
}
}
const RHO_MAX: ReciprocalLength = ReciprocalLength {
dimension: uom::lib::marker::PhantomData,
units: uom::lib::marker::PhantomData,
value: 1.0 / INNER_CATHODE_RADIUS,
};
struct HoughSpaceAccumulator {
rho_bins: u32,
theta_bins: u32,
accumulator: IndexMap<(u32, u32), Vec<SpacePoint>>,
}
fn u_v(point: SpacePoint) -> (ReciprocalLength, ReciprocalLength) {
let u = point.x() / point.r.powi(P2::new());
let v = point.y() / point.r.powi(P2::new());
(u, v)
}
impl HoughSpaceAccumulator {
fn get_bins(&self, point: SpacePoint) -> Vec<(u32, u32)> {
let (u, v) = u_v(point);
let delta_theta = Angle::FULL_TURN / f64::from(self.theta_bins);
let delta_rho = RHO_MAX / f64::from(self.rho_bins);
let mut bins = Vec::new();
let mut prev_rho_bin = (u / delta_rho).get::<ratio>().floor() as i32;
for theta_bin in 1..=self.theta_bins {
let theta = f64::from(theta_bin) * delta_theta;
let (sin, cos) = theta.sin_cos();
let rho = u * cos + v * sin;
let rho_bin = (rho / delta_rho).get::<ratio>().floor() as i32;
if !rho_bin.is_negative() || !prev_rho_bin.is_negative() {
let min_bin = prev_rho_bin.min(rho_bin);
let max_bin = prev_rho_bin.max(rho_bin);
for bin in min_bin.max(0)..=max_bin {
bins.push((theta_bin - 1, bin.try_into().unwrap()));
}
}
prev_rho_bin = rho_bin;
}
bins
}
fn add(&mut self, point: SpacePoint) {
for bin in self.get_bins(point) {
self.accumulator.entry(bin).or_default().push(point);
}
}
fn remove_unchecked(&mut self, point: SpacePoint) {
for bin in self.get_bins(point) {
let vec = self.accumulator.get_mut(&bin).unwrap();
let pos = vec.iter().position(|p| *p == point).unwrap();
vec.swap_remove(pos);
}
}
fn most_popular(&self) -> Vec<SpacePoint> {
self.accumulator
.values()
.max_by_key(|v| v.len())
.cloned()
.unwrap_or_default()
}
}
fn largest_cluster(mut points: Vec<SpacePoint>, max_distance: Length) -> Vec<SpacePoint> {
let mut clusters: Vec<Vec<_>> = Vec::new();
while let Some(point) = points.pop() {
let mut cluster = vec![point];
let mut i = 0;
while i < cluster.len() {
let mut j = 0;
while j < points.len() {
if cluster[i].distance(points[j]) <= max_distance {
cluster.push(points.swap_remove(j));
} else {
j += 1;
}
}
i += 1;
}
clusters.push(cluster);
}
clusters
.into_iter()
.max_by_key(|c| c.len())
.unwrap_or_default()
}