use crate::reconstruction::{Coordinate, Track, VertexInfo, VertexingResult};
use crate::SpacePoint;
use argmin::core::{CostFunction, Error, Executor};
use argmin::solver::neldermead::NelderMead;
use itertools::Itertools;
use uom::si::area::square_meter;
use uom::si::f64::{Area, Length};
use uom::si::length::meter;
use uom::typenum::P2;
#[allow(clippy::too_many_arguments)]
pub(crate) fn find_vertices(
mut tracks: Vec<Track>,
min_track_length: Length,
max_track_beamline_dca: Length,
max_beamline_clustering_distance: Length,
initial_simplex_delta: f64,
max_num_closest_t_iter: usize,
closest_t_tolerance: f64,
max_num_solver_iter: u64,
nelder_mead_sd_tolerance: f64,
) -> VertexingResult {
let primary_tracks = tracks
.iter()
.filter(|track| track.helix.arc_length(track.t_inner(), track.t_outer()) > min_track_length)
.filter(|track| {
(track.helix.r - track.helix.x0.hypot(track.helix.y0)).abs() < max_track_beamline_dca
})
.copied()
.collect();
let vertex = beamline_clusters(primary_tracks, max_beamline_clustering_distance)
.into_iter()
.filter(|(cluster, _)| cluster.len() > 1)
.max_set_by_key(|(cluster, _)| cluster.len())
.into_iter()
.max_by(|(c_a, _), (c_b, _)| {
c_a.iter()
.map(|track| track.helix.r)
.sum::<Length>()
.partial_cmp(&c_b.iter().map(|track| track.helix.r).sum::<Length>())
.unwrap()
})
.map(|(tracks, mean_z)| {
let initial_guess = vec![0.0, 0.0, mean_z.get::<meter>()];
let mut initial_simplex = vec![initial_guess.clone()];
for i in 0..initial_guess.len() {
let mut new_point = initial_guess.clone();
if new_point[i] == 0.0 {
new_point[i] = 0.00025;
} else {
new_point[i] *= 1.0 + initial_simplex_delta;
}
initial_simplex.push(new_point);
}
let problem = Problem {
tracks: tracks.clone(),
tolerance: closest_t_tolerance,
max_num_iter: max_num_closest_t_iter,
};
let solver = NelderMead::new(initial_simplex)
.with_sd_tolerance(nelder_mead_sd_tolerance)
.unwrap();
let res = Executor::new(problem, solver)
.configure(|state| state.max_iters(max_num_solver_iter))
.run()
.unwrap();
let best_params = res.state.best_param.unwrap();
let position = Coordinate {
x: Length::new::<meter>(best_params[0]),
y: Length::new::<meter>(best_params[1]),
z: Length::new::<meter>(best_params[2]),
};
VertexInfo {
position,
tracks: tracks
.into_iter()
.map(|track| {
let sp = SpacePoint {
r: position.x.hypot(position.y),
phi: position.y.atan2(position.x),
z: position.z,
};
let t =
track
.helix
.closest_t(sp, closest_t_tolerance, max_num_closest_t_iter);
(track, t)
})
.collect(),
}
});
for (track, _) in vertex.iter().flat_map(|v| v.tracks.iter()) {
let index = tracks.iter().position(|t| t == track).unwrap();
tracks.swap_remove(index);
}
VertexingResult {
primary: vertex,
secondaries: Vec::new(),
remainder: tracks,
}
}
fn beamline_clusters(
mut tracks: Vec<Track>,
max_beamline_clustering_distance: Length,
) -> Vec<(Vec<Track>, Length)> {
if tracks.is_empty() {
return Vec::new();
}
tracks.sort_unstable_by(|a, b| {
a.helix
.closest_to_beamline()
.z
.partial_cmp(&b.helix.closest_to_beamline().z)
.unwrap()
});
let mut clusters = vec![vec![tracks[0]]];
for track in tracks.into_iter().skip(1) {
let current_z = track.helix.closest_to_beamline().z;
let last_z = clusters
.last()
.unwrap()
.last()
.unwrap()
.helix
.closest_to_beamline()
.z;
if (current_z - last_z).abs() < max_beamline_clustering_distance {
clusters.last_mut().unwrap().push(track);
} else {
clusters.push(vec![track]);
}
}
clusters
.into_iter()
.map(|tracks| {
let z = tracks
.iter()
.map(|track| track.helix.closest_to_beamline().z)
.sum::<Length>()
/ tracks.len() as f64;
(tracks, z)
})
.collect()
}
struct Problem {
tracks: Vec<Track>,
tolerance: f64,
max_num_iter: usize,
}
fn norm_sqr(sp: SpacePoint, c: Coordinate) -> Area {
let x = c.x - sp.x();
let y = c.y - sp.y();
let z = c.z - sp.z;
x.powi(P2::new()) + y.powi(P2::new()) + z.powi(P2::new())
}
impl CostFunction for Problem {
type Param = Vec<f64>;
type Output = f64;
fn cost(&self, p: &Self::Param) -> Result<Self::Output, Error> {
let x = Length::new::<meter>(p[0]);
let y = Length::new::<meter>(p[1]);
let z = Length::new::<meter>(p[2]);
let sp = SpacePoint {
r: x.hypot(y),
phi: y.atan2(x),
z,
};
Ok(self
.tracks
.iter()
.map(|track| {
let t = track.helix.closest_t(sp, self.tolerance, self.max_num_iter);
let closest_point = track.at(t);
let val = norm_sqr(sp, closest_point);
assert!(!val.is_nan(), "found NaN in vertex_fitting::cost_function");
val
})
.sum::<Area>()
.get::<square_meter>())
}
}