use nalgebra::{allocator::Allocator, DefaultAllocator, DimName, OMatrix, OVector, RealField};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MeanError {
EmptyInput,
LengthMismatch,
NoPositiveWeights,
NotConverged,
InvalidTolerance,
IndexOutOfBounds,
}
pub enum InitialGuess<M> {
First,
MaxWeight,
Index(usize),
Provided(M),
}
#[inline]
fn choose_initial_index<T: RealField + Copy>(
weights: &[T],
max_index_exclusive: usize,
) -> Option<usize> {
use core::cmp::min;
let limit = min(max_index_exclusive, weights.len());
let mut best: Option<(usize, T)> = None;
let zero = T::zero();
for (i, &w) in weights.iter().take(limit).enumerate() {
if w > zero {
match best {
None => best = Some((i, w)),
Some((_, best_w)) if w > best_w => best = Some((i, w)),
_ => {}
}
}
}
best.map(|(i, _)| i)
}
pub trait Manifold<TangentDim: DimName, T: RealField + Copy>: Clone + Sized
where
DefaultAllocator: Allocator<TangentDim>,
{
fn retract(&self, delta: &OVector<T, TangentDim>) -> Self;
fn local(&self, other: &Self) -> OVector<T, TangentDim>;
fn weighted_mean(
points: &[Self],
weights: &[T],
tolerance: T,
initial_guess: InitialGuess<Self>,
max_iterations: usize,
) -> Result<Self, MeanError>
where
Self: Manifold<TangentDim, T>,
TangentDim: DimName,
T: RealField + Copy,
DefaultAllocator: Allocator<TangentDim>,
{
if points.is_empty() || weights.is_empty() {
return Err(MeanError::EmptyInput);
}
if tolerance < T::zero() {
return Err(MeanError::InvalidTolerance);
}
if max_iterations == 0 {
return Err(MeanError::NotConverged);
}
let mut mean = match initial_guess {
InitialGuess::First => points[0].clone(),
InitialGuess::Index(idx) => {
if idx >= points.len() {
return Err(MeanError::IndexOutOfBounds);
}
points[idx].clone()
}
InitialGuess::MaxWeight => match choose_initial_index(weights, points.len()) {
Some(idx) => points[idx].clone(),
None => return Err(MeanError::NoPositiveWeights),
},
InitialGuess::Provided(m) => m,
};
let zero = T::zero();
let tolerance_sq = tolerance * tolerance;
for _iter in 0..max_iterations {
let mut delta = OVector::<T, TangentDim>::zeros();
let mut total_weight = T::zero();
for (point, &weight) in points.iter().zip(weights.iter()) {
if weight > zero {
let mut tangent = mean.local(point);
tangent *= weight;
delta += tangent;
total_weight += weight;
}
}
if total_weight <= zero {
return Err(MeanError::NoPositiveWeights);
}
delta /= total_weight;
let delta_sq = delta.dot(&delta);
if delta_sq <= tolerance_sq {
return Ok(mean); }
mean = mean.retract(&delta);
}
Err(MeanError::NotConverged)
}
fn batch_retract(points: &[Self], deltas: &[OVector<T, TangentDim>], output: &mut [Self])
where
Self: Manifold<TangentDim, T>,
TangentDim: DimName,
T: RealField + Copy,
DefaultAllocator: Allocator<TangentDim>,
{
assert_eq!(
points.len(),
deltas.len(),
"Points and deltas length mismatch"
);
assert_eq!(
points.len(),
output.len(),
"Points and output length mismatch"
);
for ((point, delta), out) in points.iter().zip(deltas.iter()).zip(output.iter_mut()) {
*out = point.retract(delta);
}
}
fn batch_local(
base_points: &[Self],
target_points: &[Self],
output: &mut [OVector<T, TangentDim>],
) where
Self: Manifold<TangentDim, T>,
TangentDim: DimName,
T: RealField + Copy,
DefaultAllocator: Allocator<TangentDim>,
{
assert_eq!(
base_points.len(),
target_points.len(),
"Base and target points length mismatch"
);
assert_eq!(
base_points.len(),
output.len(),
"Base points and output length mismatch"
);
for ((base, target), out) in base_points
.iter()
.zip(target_points.iter())
.zip(output.iter_mut())
{
*out = base.local(target);
}
}
fn batch_local_from_base(
base_point: &Self,
target_points: &[Self],
output: &mut [OVector<T, TangentDim>],
) where
Self: Manifold<TangentDim, T>,
TangentDim: DimName,
T: RealField + Copy,
DefaultAllocator: Allocator<TangentDim>,
{
assert_eq!(
target_points.len(),
output.len(),
"Target points and output length mismatch"
);
for (target, out) in target_points.iter().zip(output.iter_mut()) {
*out = base_point.local(target);
}
}
fn batch_local_into_matrix<C>(
base_point: &Self,
target_points: &[Self],
output_matrix: &mut OMatrix<T, TangentDim, C>,
) where
Self: Manifold<TangentDim, T>,
TangentDim: DimName,
C: DimName,
T: RealField + Copy,
DefaultAllocator: Allocator<TangentDim> + Allocator<TangentDim, C>,
{
assert_eq!(
target_points.len(),
output_matrix.ncols(),
"Target points length must match matrix columns"
);
for (i, target) in target_points.iter().enumerate() {
let tangent = base_point.local(target);
output_matrix.column_mut(i).copy_from(&tangent);
}
}
}
pub trait ErrorState<Dim: DimName, T: RealField + Copy>: Clone
where
DefaultAllocator: Allocator<Dim>,
{
fn zeros() -> Self;
fn from_vector(v: OVector<T, Dim>) -> Self;
fn into_vector(self) -> OVector<T, Dim>;
fn as_vector(&self) -> OVector<T, Dim> {
self.clone().into_vector()
}
}
pub trait ManifoldProcess<State, Control: DimName, T: RealField + Copy>
where
DefaultAllocator: Allocator<Control>,
{
fn predict(&self, state: &State, dt: T, control: Option<&OVector<T, Control>>) -> State;
}
pub trait ManifoldMeasurement<
State: Manifold<TangentDim, T>,
TangentDim: DimName,
MeasDim: DimName,
T: RealField + Copy,
> where
DefaultAllocator: Allocator<MeasDim> + Allocator<TangentDim>,
{
fn measure(&self, state: &State) -> OVector<T, MeasDim>;
fn residual(
&self,
predicted: &OVector<T, MeasDim>,
measured: &OVector<T, MeasDim>,
) -> OVector<T, MeasDim>;
fn innovation(
&self,
measured: &OVector<T, MeasDim>,
predicted_mean: &OVector<T, MeasDim>,
) -> OVector<T, MeasDim> {
self.residual(predicted_mean, measured)
}
}
pub mod composite;
pub mod euclidean;
pub mod quaternion;