use super::{InitialGuess, Manifold, MeanError};
use nalgebra::{allocator::Allocator, DefaultAllocator, DimName, OMatrix, OVector, RealField};
#[derive(Clone, Debug, PartialEq)]
pub struct EuclideanManifold<T, Dim>
where
Dim: DimName,
T: RealField + Copy,
DefaultAllocator: Allocator<Dim>,
{
pub vector: OVector<T, Dim>,
}
impl<T, Dim> EuclideanManifold<T, Dim>
where
Dim: DimName,
T: RealField + Copy,
DefaultAllocator: Allocator<Dim>,
{
pub fn new(vector: OVector<T, Dim>) -> Self {
Self { vector }
}
pub fn as_vector(&self) -> &OVector<T, Dim> {
&self.vector
}
pub fn as_vector_mut(&mut self) -> &mut OVector<T, Dim> {
&mut self.vector
}
pub fn into_vector(self) -> OVector<T, Dim> {
self.vector
}
}
impl<T, Dim> Manifold<Dim, T> for EuclideanManifold<T, Dim>
where
Dim: DimName,
T: RealField + Copy,
DefaultAllocator: Allocator<Dim> + Allocator<Dim, Dim>,
{
fn retract(&self, delta: &OVector<T, Dim>) -> Self {
Self::new(&self.vector + delta)
}
fn local(&self, other: &Self) -> OVector<T, Dim> {
&other.vector - &self.vector
}
fn weighted_mean(
points: &[Self],
weights: &[T],
_tolerance: T,
_initial_guess: InitialGuess<Self>,
_max_iterations: usize,
) -> Result<Self, MeanError>
where
DefaultAllocator: Allocator<Dim>,
{
if points.is_empty() || weights.is_empty() {
return Err(MeanError::EmptyInput);
}
if points.len() != weights.len() {
return Err(MeanError::LengthMismatch);
}
let mut weighted_sum = OVector::<T, Dim>::zeros();
let mut total_weight = T::zero();
for (point, &weight) in points.iter().zip(weights.iter()) {
if weight > T::zero() {
weighted_sum += &point.vector * weight;
total_weight += weight;
}
}
if total_weight <= T::zero() {
return Err(MeanError::NoPositiveWeights);
}
let mean_vector = weighted_sum / total_weight;
Ok(Self::new(mean_vector))
}
fn batch_retract(points: &[Self], deltas: &[OVector<T, Dim>], output: &mut [Self])
where
DefaultAllocator: Allocator<Dim>,
{
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, Dim>])
where
DefaultAllocator: Allocator<Dim>,
{
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, Dim>],
) where
DefaultAllocator: Allocator<Dim>,
{
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, Dim, C>,
) where
C: DimName,
DefaultAllocator: Allocator<Dim> + Allocator<Dim, 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);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
use nalgebra::{Vector2, Vector3, U2};
#[test]
fn test_euclidean_construction() {
let point = EuclideanManifold::new(Vector3::new(1.0, 2.0, 3.0));
assert_eq!(point.as_vector(), &Vector3::new(1.0, 2.0, 3.0));
}
#[test]
fn test_euclidean_retract_local() {
let point1 = EuclideanManifold::new(Vector2::new(1.0, 2.0));
let point2 = EuclideanManifold::new(Vector2::new(3.0, 4.0));
let delta = point1.local(&point2);
assert_eq!(delta, Vector2::new(2.0, 2.0));
let retracted = point1.retract(&delta);
assert_eq!(retracted.as_vector(), point2.as_vector());
}
#[test]
fn test_euclidean_weighted_mean() {
let point1 = EuclideanManifold::new(Vector2::new(0.0, 0.0));
let point2 = EuclideanManifold::new(Vector2::new(2.0, 4.0));
let point3 = EuclideanManifold::new(Vector2::new(4.0, 0.0));
let points = vec![point1, point2, point3];
let weights = vec![0.5, 0.25, 0.25];
let mean = EuclideanManifold::<f64, U2>::weighted_mean(
&points,
&weights,
1e-9,
InitialGuess::First,
10,
)
.unwrap();
assert!((mean.as_vector() - Vector2::new(1.5, 1.0)).norm() < 1e-10);
}
#[test]
fn test_euclidean_batch_operations() {
let points = vec![
EuclideanManifold::new(Vector2::new(1.0, 2.0)),
EuclideanManifold::new(Vector2::new(3.0, 4.0)),
];
let deltas = vec![Vector2::new(0.5, 0.5), Vector2::new(-0.5, -0.5)];
let mut retracted = vec![EuclideanManifold::new(Vector2::zeros()); points.len()];
EuclideanManifold::batch_retract(&points, &deltas, &mut retracted);
assert_eq!(retracted[0].as_vector(), &Vector2::new(1.5, 2.5));
assert_eq!(retracted[1].as_vector(), &Vector2::new(2.5, 3.5));
let base_point = EuclideanManifold::new(Vector2::new(0.0, 0.0));
let mut locals = vec![Vector2::zeros(); points.len()];
EuclideanManifold::batch_local_from_base(&base_point, &points, &mut locals);
assert_eq!(locals[0], Vector2::new(1.0, 2.0));
assert_eq!(locals[1], Vector2::new(3.0, 4.0));
}
}