1use crate::{LieAlgebra, LieGroup};
55use std::fmt;
56use std::ops::{Add, Mul, MulAssign, Neg, Sub};
57
58#[derive(Clone, Copy, Debug, PartialEq)]
78pub struct RPlusAlgebra(pub(crate) f64);
79
80impl Add for RPlusAlgebra {
81 type Output = Self;
82 fn add(self, rhs: Self) -> Self {
83 Self(self.0 + rhs.0)
84 }
85}
86
87impl Add<&RPlusAlgebra> for RPlusAlgebra {
88 type Output = RPlusAlgebra;
89 fn add(self, rhs: &RPlusAlgebra) -> RPlusAlgebra {
90 self + *rhs
91 }
92}
93
94impl Add<RPlusAlgebra> for &RPlusAlgebra {
95 type Output = RPlusAlgebra;
96 fn add(self, rhs: RPlusAlgebra) -> RPlusAlgebra {
97 *self + rhs
98 }
99}
100
101impl Add<&RPlusAlgebra> for &RPlusAlgebra {
102 type Output = RPlusAlgebra;
103 fn add(self, rhs: &RPlusAlgebra) -> RPlusAlgebra {
104 *self + *rhs
105 }
106}
107
108impl Sub for RPlusAlgebra {
109 type Output = Self;
110 fn sub(self, rhs: Self) -> Self {
111 Self(self.0 - rhs.0)
112 }
113}
114
115impl Neg for RPlusAlgebra {
116 type Output = Self;
117 fn neg(self) -> Self {
118 Self(-self.0)
119 }
120}
121
122impl Mul<f64> for RPlusAlgebra {
123 type Output = Self;
124 fn mul(self, scalar: f64) -> Self {
125 Self(self.0 * scalar)
126 }
127}
128
129impl Mul<RPlusAlgebra> for f64 {
130 type Output = RPlusAlgebra;
131 fn mul(self, rhs: RPlusAlgebra) -> RPlusAlgebra {
132 rhs * self
133 }
134}
135
136impl RPlusAlgebra {
137 #[must_use]
141 pub fn new(value: f64) -> Self {
142 Self(value)
143 }
144
145 #[must_use]
147 pub fn value(&self) -> f64 {
148 self.0
149 }
150}
151
152impl LieAlgebra for RPlusAlgebra {
153 const DIM: usize = 1;
154
155 fn zero() -> Self {
156 Self(0.0)
157 }
158
159 fn add(&self, other: &Self) -> Self {
160 Self(self.0 + other.0)
161 }
162
163 fn scale(&self, scalar: f64) -> Self {
164 Self(self.0 * scalar)
165 }
166
167 fn norm(&self) -> f64 {
168 self.0.abs()
169 }
170
171 fn basis_element(i: usize) -> Self {
172 assert_eq!(i, 0, "ℝ⁺ algebra is 1-dimensional");
173 Self(1.0)
174 }
175
176 fn from_components(components: &[f64]) -> Self {
177 assert_eq!(components.len(), 1, "ℝ⁺ algebra has dimension 1");
178 Self(components[0])
179 }
180
181 fn to_components(&self) -> Vec<f64> {
182 vec![self.0]
183 }
184
185 fn bracket(&self, _other: &Self) -> Self {
187 Self::zero()
188 }
189
190 #[inline]
191 fn inner(&self, other: &Self) -> f64 {
192 self.0 * other.0
193 }
194}
195
196#[derive(Clone, Copy, Debug, PartialEq)]
227pub struct RPlus {
228 value: f64,
230}
231
232impl RPlus {
233 #[must_use]
248 pub fn from_value(value: f64) -> Self {
249 assert!(value > 0.0, "ℝ⁺ elements must be positive, got {}", value);
250 Self { value }
251 }
252
253 #[must_use]
267 pub fn from_value_clamped(value: f64) -> Self {
268 Self {
269 value: value.max(1e-10),
270 }
271 }
272
273 #[must_use]
275 pub fn value(&self) -> f64 {
276 self.value
277 }
278
279 #[must_use]
295 pub fn from_log(log_value: f64) -> Self {
296 Self {
297 value: log_value.exp(),
298 }
299 }
300
301 #[must_use]
309 pub fn scaling(magnitude: f64) -> Self {
310 Self::from_log(magnitude)
311 }
312
313 #[cfg(feature = "rand")]
322 #[must_use]
323 pub fn random<R: rand::Rng>(rng: &mut R, log_mean: f64, log_std: f64) -> Self {
324 use rand::distributions::Distribution;
325 use rand_distr::Normal;
326 let normal =
327 Normal::new(log_mean, log_std).expect("log_std must be non-negative and finite");
328 Self::from_log(normal.sample(rng))
329 }
330}
331
332impl approx::AbsDiffEq for RPlusAlgebra {
333 type Epsilon = f64;
334
335 fn default_epsilon() -> Self::Epsilon {
336 1e-10
337 }
338
339 fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
340 (self.0 - other.0).abs() < epsilon
341 }
342}
343
344impl approx::RelativeEq for RPlusAlgebra {
345 fn default_max_relative() -> Self::Epsilon {
346 1e-10
347 }
348
349 fn relative_eq(
350 &self,
351 other: &Self,
352 epsilon: Self::Epsilon,
353 max_relative: Self::Epsilon,
354 ) -> bool {
355 approx::RelativeEq::relative_eq(&self.0, &other.0, epsilon, max_relative)
356 }
357}
358
359impl fmt::Display for RPlusAlgebra {
360 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
361 write!(f, "r+({:.4})", self.0)
362 }
363}
364
365impl Mul<&RPlus> for &RPlus {
367 type Output = RPlus;
368 fn mul(self, rhs: &RPlus) -> RPlus {
369 self.compose(rhs)
370 }
371}
372
373impl Mul<&RPlus> for RPlus {
374 type Output = RPlus;
375 fn mul(self, rhs: &RPlus) -> RPlus {
376 self.compose(rhs)
377 }
378}
379
380impl MulAssign<&RPlus> for RPlus {
381 fn mul_assign(&mut self, rhs: &RPlus) {
382 *self = self.compose(rhs);
383 }
384}
385
386impl LieGroup for RPlus {
387 const MATRIX_DIM: usize = 1;
388
389 type Algebra = RPlusAlgebra;
390
391 fn identity() -> Self {
392 Self { value: 1.0 }
393 }
394
395 fn compose(&self, other: &Self) -> Self {
396 Self {
397 value: self.value * other.value,
398 }
399 }
400
401 fn inverse(&self) -> Self {
402 Self {
403 value: 1.0 / self.value,
404 }
405 }
406
407 fn conjugate_transpose(&self) -> Self {
408 self.inverse()
411 }
412
413 fn adjoint_action(&self, algebra_element: &RPlusAlgebra) -> RPlusAlgebra {
414 *algebra_element
416 }
417
418 fn distance_to_identity(&self) -> f64 {
419 self.value.ln().abs()
421 }
422
423 fn exp(tangent: &RPlusAlgebra) -> Self {
424 Self::from_log(tangent.0)
426 }
427
428 fn log(&self) -> crate::error::LogResult<RPlusAlgebra> {
429 Ok(RPlusAlgebra(self.value.ln()))
434 }
435}
436
437impl std::fmt::Display for RPlus {
439 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
440 write!(f, "ℝ⁺({:.4})", self.value)
441 }
442}
443
444impl crate::Abelian for RPlus {}
452
453#[cfg(test)]
458mod tests {
459 use super::*;
460
461 #[test]
462 fn test_identity() {
463 let e = RPlus::identity();
464 assert!((e.value() - 1.0).abs() < 1e-10);
465 }
466
467 #[test]
468 fn test_compose() {
469 let a = RPlus::from_value(2.0);
470 let b = RPlus::from_value(3.0);
471 let product = a.compose(&b);
472 assert!((product.value() - 6.0).abs() < 1e-10);
473 }
474
475 #[test]
476 fn test_inverse() {
477 let a = RPlus::from_value(4.0);
478 let a_inv = a.inverse();
479 assert!((a_inv.value() - 0.25).abs() < 1e-10);
480
481 let product = a.compose(&a_inv);
483 assert!((product.value() - 1.0).abs() < 1e-10);
484 }
485
486 #[test]
487 fn test_exp_log_roundtrip() {
488 let x = RPlusAlgebra(1.5);
489 let g = RPlus::exp(&x);
490 let x_back = g.log().unwrap();
491 assert!((x_back.value() - x.value()).abs() < 1e-10);
492 }
493
494 #[test]
495 fn test_log_exp_roundtrip() {
496 let g = RPlus::from_value(std::f64::consts::E);
497 let x = g.log().unwrap();
498 let g_back = RPlus::exp(&x);
499 assert!((g_back.value() - g.value()).abs() < 1e-10);
500 }
501
502 #[test]
503 fn test_distance_to_identity() {
504 let e = RPlus::identity();
505 assert!(e.distance_to_identity() < 1e-10);
506
507 let g = RPlus::from_value(std::f64::consts::E);
508 assert!((g.distance_to_identity() - 1.0).abs() < 1e-10);
509
510 let a = RPlus::from_value(2.0);
512 let b = RPlus::from_value(0.5);
513 assert!((a.distance_to_identity() - b.distance_to_identity()).abs() < 1e-10);
514 }
515
516 #[test]
517 fn test_algebra_operations() {
518 let x = RPlusAlgebra(1.0);
519 let y = RPlusAlgebra(2.0);
520
521 let sum = x.add(&y);
522 assert!((sum.value() - 3.0).abs() < 1e-10);
523
524 let scaled = x.scale(3.0);
525 assert!((scaled.value() - 3.0).abs() < 1e-10);
526
527 let zero = RPlusAlgebra::zero();
528 assert!(zero.value().abs() < 1e-10);
529 }
530
531 #[test]
532 fn test_abelian_property() {
533 let a = RPlus::from_value(2.0);
534 let b = RPlus::from_value(3.0);
535
536 let ab = a.compose(&b);
537 let ba = b.compose(&a);
538
539 assert!((ab.value() - ba.value()).abs() < 1e-10);
540 }
541
542 #[test]
543 fn test_from_value_clamped() {
544 let g = RPlus::from_value_clamped(-0.5);
546 assert!(g.value() > 0.0);
547 assert!(g.value() >= 1e-10);
548
549 let h = RPlus::from_value_clamped(0.0);
551 assert!(h.value() > 0.0);
552
553 let k = RPlus::from_value_clamped(2.5);
555 assert!((k.value() - 2.5).abs() < 1e-10);
556 }
557
558 #[test]
559 fn test_scaling() {
560 let s0 = RPlus::scaling(0.0);
562 assert!((s0.value() - 1.0).abs() < 1e-10);
563
564 let s1 = RPlus::scaling(1.0);
566 assert!((s1.value() - std::f64::consts::E).abs() < 1e-10);
567
568 let sm1 = RPlus::scaling(-1.0);
570 assert!((sm1.value() - 1.0 / std::f64::consts::E).abs() < 1e-10);
571 }
572
573 #[test]
574 #[cfg(feature = "rand")]
575 fn test_random() {
576 use rand::SeedableRng;
577 let mut rng = rand::rngs::StdRng::seed_from_u64(42);
578
579 for _ in 0..100 {
581 let g = RPlus::random(&mut rng, 0.0, 1.0);
582 assert!(g.value() > 0.0);
583 }
584
585 let mut log_sum = 0.0;
587 let n = 1000;
588 for _ in 0..n {
589 let g = RPlus::random(&mut rng, 0.5, 0.1);
590 log_sum += g.value().ln();
591 }
592 let log_mean = log_sum / n as f64;
593 assert!(
594 (log_mean - 0.5).abs() < 0.1,
595 "Log mean should be approximately 0.5"
596 );
597 }
598
599 #[test]
600 fn test_adjoint() {
601 let g = RPlus::from_value(3.0);
603 let adj = g.conjugate_transpose();
604 assert!(
605 (adj.value() - 1.0 / 3.0).abs() < 1e-10,
606 "Adjoint should equal inverse"
607 );
608 }
609
610 #[test]
611 fn test_adjoint_action() {
612 let g = RPlus::from_value(5.0);
614 let x = RPlusAlgebra(2.5);
615 let result = g.adjoint_action(&x);
616 assert!((result.value() - x.value()).abs() < 1e-10);
617 }
618
619 #[test]
620 fn test_display() {
621 let g = RPlus::from_value(2.5);
622 let s = format!("{}", g);
623 assert!(s.contains("2.5"));
624 assert!(s.contains("ℝ⁺"));
625 }
626
627 #[test]
628 fn test_algebra_dim() {
629 assert_eq!(RPlusAlgebra::DIM, 1);
630 }
631
632 #[test]
633 fn test_algebra_basis_element() {
634 let basis = RPlusAlgebra::basis_element(0);
635 assert!((basis.value() - 1.0).abs() < 1e-10);
636 }
637
638 #[test]
639 #[should_panic(expected = "1-dimensional")]
640 fn test_algebra_basis_element_out_of_bounds() {
641 let _ = RPlusAlgebra::basis_element(1);
642 }
643
644 #[test]
645 fn test_algebra_from_to_components() {
646 let x = RPlusAlgebra::from_components(&[3.5]);
647 assert!((x.value() - 3.5).abs() < 1e-10);
648
649 let comps = x.to_components();
650 assert_eq!(comps.len(), 1);
651 assert!((comps[0] - 3.5).abs() < 1e-10);
652 }
653
654 #[test]
655 #[should_panic(expected = "dimension 1")]
656 fn test_algebra_from_components_wrong_dim() {
657 let _ = RPlusAlgebra::from_components(&[1.0, 2.0]);
658 }
659
660 #[test]
661 fn test_algebra_norm() {
662 let x = RPlusAlgebra(-3.0);
663 assert!((x.norm() - 3.0).abs() < 1e-10);
664
665 let y = RPlusAlgebra(2.5);
666 assert!((y.norm() - 2.5).abs() < 1e-10);
667 }
668
669 #[test]
670 fn test_from_log() {
671 let g = RPlus::from_log(0.0);
673 assert!((g.value() - 1.0).abs() < 1e-10);
674
675 let h = RPlus::from_log(1.0);
677 assert!((h.value() - std::f64::consts::E).abs() < 1e-10);
678
679 let k = RPlus::from_log(-1.0);
681 assert!((k.value() - 1.0 / std::f64::consts::E).abs() < 1e-10);
682 }
683
684 #[test]
685 fn test_group_dim() {
686 assert_eq!(RPlus::MATRIX_DIM, 1);
687 }
688
689 #[test]
690 #[should_panic(expected = "positive")]
691 fn test_from_value_panics_on_zero() {
692 let _ = RPlus::from_value(0.0);
693 }
694
695 #[test]
696 #[should_panic(expected = "positive")]
697 fn test_from_value_panics_on_negative() {
698 let _ = RPlus::from_value(-1.0);
699 }
700}