use crate::SpacePoint;
use core::slice::Iter;
use std::f64::consts::PI;
use thiserror::Error;
use uom::si::angle::radian;
use uom::si::f64::{Angle, Length, Ratio};
use uom::si::length::{centimeter, meter};
use uom::si::ratio::ratio;
use uom::typenum::P2;
mod track_finding;
mod track_fitting;
mod vertex_fitting;
#[derive(Clone, Debug)]
pub struct Cluster(Vec<SpacePoint>);
impl Cluster {
pub fn iter(&self) -> Iter<'_, SpacePoint> {
self.0.iter()
}
}
impl<'a> IntoIterator for &'a Cluster {
type Item = &'a SpacePoint;
type IntoIter = Iter<'a, SpacePoint>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl IntoIterator for Cluster {
type Item = SpacePoint;
type IntoIter = std::vec::IntoIter<SpacePoint>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
#[derive(Clone, Debug)]
pub struct ClusteringResult {
pub clusters: Vec<Cluster>,
pub remainder: Vec<SpacePoint>,
}
pub fn cluster_spacepoints(sp: Vec<SpacePoint>) -> ClusteringResult {
track_finding::cluster_spacepoints(
sp,
13,
250,
230,
Length::new::<centimeter>(3.0),
)
}
#[derive(Clone, Copy, Debug)]
pub struct Coordinate {
pub x: Length,
pub y: Length,
pub z: Length,
}
#[derive(Clone, Copy, Debug, PartialEq)]
struct Helix {
x0: Length,
y0: Length,
z0: Length,
r: Length,
phi0: Angle,
h: Length,
}
fn angle_between_vectors(v1: (Length, Length), v2: (Length, Length)) -> Angle {
let dot = v1.0 * v2.0 + v1.1 * v2.1;
let det = v1.0 * v2.1 - v1.1 * v2.0;
det.atan2(dot)
}
impl Helix {
fn at(&self, t: f64) -> Coordinate {
let t = Angle::new::<radian>(t);
Coordinate {
x: self.r * (t + self.phi0).cos() + self.x0,
y: self.r * (t + self.phi0).sin() + self.y0,
z: (self.h / Angle::FULL_TURN) * t + self.z0,
}
}
#[allow(non_snake_case)]
fn closest_t(&self, p: SpacePoint, tolerance: f64, max_num_iter: usize) -> f64 {
if self.h.abs() < Length::new::<meter>(f64::EPSILON) {
let c = self.at(0.0);
return angle_between_vectors(
(c.x - self.x0, c.y - self.y0),
(p.x() - self.x0, p.y() - self.y0),
)
.get::<radian>();
}
let tolerance = Angle::new::<radian>(tolerance.abs());
let u = p.x();
let v = p.y();
let r = (u - self.x0).hypot(v - self.y0);
let delta = (v - self.y0).atan2(u - self.x0);
let temp = self.phi0 + Angle::from(2.0 * PI * (p.z - self.z0) / self.h) - delta;
let n = (temp / Angle::FULL_TURN).floor::<ratio>();
let M = Angle::HALF_TURN + Angle::from(2.0 * PI * n) - temp;
let e = 4.0 * PI.powi(2) * r * self.r / self.h.powi(P2::new());
fn f(E: Angle, e: Ratio, M: Angle) -> Angle {
E - Angle::from(e * E.sin()) - M
}
fn df(E: Angle, e: Ratio) -> Ratio {
Ratio::new::<ratio>(1.0) - e * E.cos()
}
let mut E = if M < Angle::new::<radian>(0.0) {
-Angle::HALF_TURN
} else {
Angle::HALF_TURN
};
for _ in 0..max_num_iter {
E -= Angle::from(f(E, e, M) / df(E, e));
if f(E, e, M).abs() < tolerance {
break;
}
}
let t = Angle::HALF_TURN - E + Angle::from(2.0 * PI * n) - self.phi0 + delta;
t.get::<radian>().clamp(-PI, PI)
}
fn closest_to_beamline(&self) -> Coordinate {
let c = self.at(0.0);
let t = angle_between_vectors((c.x - self.x0, c.y - self.y0), (-self.x0, -self.y0))
.get::<radian>();
self.at(t)
}
fn arc_length(&self, t1: f64, t2: f64) -> Length {
let delta_t = (t2 - t1).abs();
let s = self.r * delta_t;
let delta_z = (self.at(t2).z - self.at(t1).z).abs();
s.hypot(delta_z)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Track {
helix: Helix,
t_inner: f64,
t_outer: f64,
}
impl Track {
pub fn at(&self, t: f64) -> Coordinate {
self.helix.at(t)
}
pub fn t_inner(&self) -> f64 {
self.t_inner
}
pub fn t_outer(&self) -> f64 {
self.t_outer
}
}
#[derive(Debug, Error)]
pub enum TryTrackFromClusterError {
#[error("unable to produce initial fit parameters")]
NoInitialParameters,
}
impl TryFrom<Cluster> for Track {
type Error = TryTrackFromClusterError;
fn try_from(cluster: Cluster) -> Result<Self, Self::Error> {
track_fitting::fit_cluster_to_helix(
cluster,
100,
f64::EPSILON,
0.05,
20,
f64::EPSILON,
)
}
}
#[derive(Clone, Debug)]
pub struct VertexInfo {
pub position: Coordinate,
pub tracks: Vec<(Track, f64)>,
}
#[derive(Clone, Debug)]
pub struct VertexingResult {
pub primary: Option<VertexInfo>,
pub secondaries: Vec<VertexInfo>,
pub remainder: Vec<Track>,
}
pub fn find_vertices(tracks: Vec<Track>) -> VertexingResult {
vertex_fitting::find_vertices(
tracks,
Length::new::<centimeter>(3.5),
Length::new::<centimeter>(5.3),
Length::new::<centimeter>(3.4),
0.05,
20,
f64::EPSILON,
100,
f64::EPSILON,
)
}
#[cfg(test)]
mod tests;