1use crate::{LieAlgebra, LieGroup};
55use num_complex::Complex;
56use std::fmt;
57use std::ops::{Add, Mul, MulAssign, Neg, Sub};
58
59#[derive(Clone, Copy, Debug, PartialEq)]
79pub struct RPlusAlgebra(pub f64);
80
81impl Add for RPlusAlgebra {
82 type Output = Self;
83 fn add(self, rhs: Self) -> Self {
84 Self(self.0 + rhs.0)
85 }
86}
87
88impl Add<&RPlusAlgebra> for RPlusAlgebra {
89 type Output = RPlusAlgebra;
90 fn add(self, rhs: &RPlusAlgebra) -> RPlusAlgebra {
91 self + *rhs
92 }
93}
94
95impl Add<RPlusAlgebra> for &RPlusAlgebra {
96 type Output = RPlusAlgebra;
97 fn add(self, rhs: RPlusAlgebra) -> RPlusAlgebra {
98 *self + rhs
99 }
100}
101
102impl Add<&RPlusAlgebra> for &RPlusAlgebra {
103 type Output = RPlusAlgebra;
104 fn add(self, rhs: &RPlusAlgebra) -> RPlusAlgebra {
105 *self + *rhs
106 }
107}
108
109impl Sub for RPlusAlgebra {
110 type Output = Self;
111 fn sub(self, rhs: Self) -> Self {
112 Self(self.0 - rhs.0)
113 }
114}
115
116impl Neg for RPlusAlgebra {
117 type Output = Self;
118 fn neg(self) -> Self {
119 Self(-self.0)
120 }
121}
122
123impl Mul<f64> for RPlusAlgebra {
124 type Output = Self;
125 fn mul(self, scalar: f64) -> Self {
126 Self(self.0 * scalar)
127 }
128}
129
130impl Mul<RPlusAlgebra> for f64 {
131 type Output = RPlusAlgebra;
132 fn mul(self, rhs: RPlusAlgebra) -> RPlusAlgebra {
133 rhs * self
134 }
135}
136
137impl RPlusAlgebra {
138 #[must_use]
140 pub fn value(&self) -> f64 {
141 self.0
142 }
143}
144
145impl LieAlgebra for RPlusAlgebra {
146 const DIM: usize = 1;
147
148 fn zero() -> Self {
149 Self(0.0)
150 }
151
152 fn add(&self, other: &Self) -> Self {
153 Self(self.0 + other.0)
154 }
155
156 fn scale(&self, scalar: f64) -> Self {
157 Self(self.0 * scalar)
158 }
159
160 fn norm(&self) -> f64 {
161 self.0.abs()
162 }
163
164 fn basis_element(i: usize) -> Self {
165 assert_eq!(i, 0, "ℝ⁺ algebra is 1-dimensional");
166 Self(1.0)
167 }
168
169 fn from_components(components: &[f64]) -> Self {
170 assert_eq!(components.len(), 1, "ℝ⁺ algebra has dimension 1");
171 Self(components[0])
172 }
173
174 fn to_components(&self) -> Vec<f64> {
175 vec![self.0]
176 }
177
178 fn bracket(&self, _other: &Self) -> Self {
180 Self::zero()
181 }
182}
183
184#[derive(Clone, Copy, Debug, PartialEq)]
215pub struct RPlus {
216 value: f64,
218}
219
220impl RPlus {
221 #[must_use]
236 pub fn from_value(value: f64) -> Self {
237 assert!(value > 0.0, "ℝ⁺ elements must be positive, got {}", value);
238 Self { value }
239 }
240
241 #[must_use]
255 pub fn from_value_clamped(value: f64) -> Self {
256 Self {
257 value: value.max(1e-10),
258 }
259 }
260
261 #[must_use]
263 pub fn value(&self) -> f64 {
264 self.value
265 }
266
267 #[must_use]
283 pub fn from_log(log_value: f64) -> Self {
284 Self {
285 value: log_value.exp(),
286 }
287 }
288
289 #[must_use]
297 pub fn scaling(magnitude: f64) -> Self {
298 Self::from_log(magnitude)
299 }
300
301 #[cfg(feature = "rand")]
310 #[must_use]
311 pub fn random<R: rand::Rng>(rng: &mut R, log_mean: f64, log_std: f64) -> Self {
312 use rand::distributions::Distribution;
313 use rand_distr::Normal;
314 let normal =
315 Normal::new(log_mean, log_std).expect("log_std must be non-negative and finite");
316 Self::from_log(normal.sample(rng))
317 }
318}
319
320impl fmt::Display for RPlusAlgebra {
321 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
322 write!(f, "r+({:.4})", self.0)
323 }
324}
325
326impl Mul<&RPlus> for &RPlus {
328 type Output = RPlus;
329 fn mul(self, rhs: &RPlus) -> RPlus {
330 self.compose(rhs)
331 }
332}
333
334impl Mul<&RPlus> for RPlus {
335 type Output = RPlus;
336 fn mul(self, rhs: &RPlus) -> RPlus {
337 self.compose(rhs)
338 }
339}
340
341impl MulAssign<&RPlus> for RPlus {
342 fn mul_assign(&mut self, rhs: &RPlus) {
343 *self = self.compose(rhs);
344 }
345}
346
347impl LieGroup for RPlus {
348 const DIM: usize = 1;
349
350 type Algebra = RPlusAlgebra;
351
352 fn identity() -> Self {
353 Self { value: 1.0 }
354 }
355
356 fn compose(&self, other: &Self) -> Self {
357 Self {
358 value: self.value * other.value,
359 }
360 }
361
362 fn inverse(&self) -> Self {
363 Self {
364 value: 1.0 / self.value,
365 }
366 }
367
368 fn adjoint(&self) -> Self {
369 self.inverse()
372 }
373
374 fn adjoint_action(&self, algebra_element: &RPlusAlgebra) -> RPlusAlgebra {
375 *algebra_element
377 }
378
379 fn distance_to_identity(&self) -> f64 {
380 self.value.ln().abs()
382 }
383
384 fn exp(tangent: &RPlusAlgebra) -> Self {
385 Self::from_log(tangent.0)
387 }
388
389 fn log(&self) -> crate::error::LogResult<RPlusAlgebra> {
390 Ok(RPlusAlgebra(self.value.ln()))
395 }
396
397 fn dim() -> usize {
398 1
399 }
400
401 fn trace(&self) -> Complex<f64> {
402 Complex::new(self.value, 0.0)
404 }
405}
406
407impl std::fmt::Display for RPlus {
409 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
410 write!(f, "ℝ⁺({:.4})", self.value)
411 }
412}
413
414impl crate::Abelian for RPlus {}
422
423#[cfg(test)]
428mod tests {
429 use super::*;
430
431 #[test]
432 fn test_identity() {
433 let e = RPlus::identity();
434 assert!((e.value() - 1.0).abs() < 1e-10);
435 }
436
437 #[test]
438 fn test_compose() {
439 let a = RPlus::from_value(2.0);
440 let b = RPlus::from_value(3.0);
441 let product = a.compose(&b);
442 assert!((product.value() - 6.0).abs() < 1e-10);
443 }
444
445 #[test]
446 fn test_inverse() {
447 let a = RPlus::from_value(4.0);
448 let a_inv = a.inverse();
449 assert!((a_inv.value() - 0.25).abs() < 1e-10);
450
451 let product = a.compose(&a_inv);
453 assert!((product.value() - 1.0).abs() < 1e-10);
454 }
455
456 #[test]
457 fn test_exp_log_roundtrip() {
458 let x = RPlusAlgebra(1.5);
459 let g = RPlus::exp(&x);
460 let x_back = g.log().unwrap();
461 assert!((x_back.value() - x.value()).abs() < 1e-10);
462 }
463
464 #[test]
465 fn test_log_exp_roundtrip() {
466 let g = RPlus::from_value(std::f64::consts::E);
467 let x = g.log().unwrap();
468 let g_back = RPlus::exp(&x);
469 assert!((g_back.value() - g.value()).abs() < 1e-10);
470 }
471
472 #[test]
473 fn test_distance_to_identity() {
474 let e = RPlus::identity();
475 assert!(e.distance_to_identity() < 1e-10);
476
477 let g = RPlus::from_value(std::f64::consts::E);
478 assert!((g.distance_to_identity() - 1.0).abs() < 1e-10);
479
480 let a = RPlus::from_value(2.0);
482 let b = RPlus::from_value(0.5);
483 assert!((a.distance_to_identity() - b.distance_to_identity()).abs() < 1e-10);
484 }
485
486 #[test]
487 fn test_algebra_operations() {
488 let x = RPlusAlgebra(1.0);
489 let y = RPlusAlgebra(2.0);
490
491 let sum = x.add(&y);
492 assert!((sum.value() - 3.0).abs() < 1e-10);
493
494 let scaled = x.scale(3.0);
495 assert!((scaled.value() - 3.0).abs() < 1e-10);
496
497 let zero = RPlusAlgebra::zero();
498 assert!(zero.value().abs() < 1e-10);
499 }
500
501 #[test]
502 fn test_abelian_property() {
503 let a = RPlus::from_value(2.0);
504 let b = RPlus::from_value(3.0);
505
506 let ab = a.compose(&b);
507 let ba = b.compose(&a);
508
509 assert!((ab.value() - ba.value()).abs() < 1e-10);
510 }
511
512 #[test]
513 fn test_from_value_clamped() {
514 let g = RPlus::from_value_clamped(-0.5);
516 assert!(g.value() > 0.0);
517 assert!(g.value() >= 1e-10);
518
519 let h = RPlus::from_value_clamped(0.0);
521 assert!(h.value() > 0.0);
522
523 let k = RPlus::from_value_clamped(2.5);
525 assert!((k.value() - 2.5).abs() < 1e-10);
526 }
527
528 #[test]
529 fn test_scaling() {
530 let s0 = RPlus::scaling(0.0);
532 assert!((s0.value() - 1.0).abs() < 1e-10);
533
534 let s1 = RPlus::scaling(1.0);
536 assert!((s1.value() - std::f64::consts::E).abs() < 1e-10);
537
538 let sm1 = RPlus::scaling(-1.0);
540 assert!((sm1.value() - 1.0 / std::f64::consts::E).abs() < 1e-10);
541 }
542
543 #[test]
544 #[cfg(feature = "rand")]
545 fn test_random() {
546 use rand::SeedableRng;
547 let mut rng = rand::rngs::StdRng::seed_from_u64(42);
548
549 for _ in 0..100 {
551 let g = RPlus::random(&mut rng, 0.0, 1.0);
552 assert!(g.value() > 0.0);
553 }
554
555 let mut log_sum = 0.0;
557 let n = 1000;
558 for _ in 0..n {
559 let g = RPlus::random(&mut rng, 0.5, 0.1);
560 log_sum += g.value().ln();
561 }
562 let log_mean = log_sum / n as f64;
563 assert!(
564 (log_mean - 0.5).abs() < 0.1,
565 "Log mean should be approximately 0.5"
566 );
567 }
568
569 #[test]
570 fn test_adjoint() {
571 let g = RPlus::from_value(3.0);
573 let adj = g.adjoint();
574 assert!(
575 (adj.value() - 1.0 / 3.0).abs() < 1e-10,
576 "Adjoint should equal inverse"
577 );
578 }
579
580 #[test]
581 fn test_adjoint_action() {
582 let g = RPlus::from_value(5.0);
584 let x = RPlusAlgebra(2.5);
585 let result = g.adjoint_action(&x);
586 assert!((result.value() - x.value()).abs() < 1e-10);
587 }
588
589 #[test]
590 fn test_trace() {
591 let g = RPlus::from_value(7.0);
592 let tr = g.trace();
593 assert!((tr.re - 7.0).abs() < 1e-10);
594 assert!(tr.im.abs() < 1e-10);
595 }
596
597 #[test]
598 fn test_display() {
599 let g = RPlus::from_value(2.5);
600 let s = format!("{}", g);
601 assert!(s.contains("2.5"));
602 assert!(s.contains("ℝ⁺"));
603 }
604
605 #[test]
606 fn test_algebra_dim() {
607 assert_eq!(RPlusAlgebra::dim(), 1);
608 }
609
610 #[test]
611 fn test_algebra_basis_element() {
612 let basis = RPlusAlgebra::basis_element(0);
613 assert!((basis.value() - 1.0).abs() < 1e-10);
614 }
615
616 #[test]
617 #[should_panic(expected = "1-dimensional")]
618 fn test_algebra_basis_element_out_of_bounds() {
619 let _ = RPlusAlgebra::basis_element(1);
620 }
621
622 #[test]
623 fn test_algebra_from_to_components() {
624 let x = RPlusAlgebra::from_components(&[3.5]);
625 assert!((x.value() - 3.5).abs() < 1e-10);
626
627 let comps = x.to_components();
628 assert_eq!(comps.len(), 1);
629 assert!((comps[0] - 3.5).abs() < 1e-10);
630 }
631
632 #[test]
633 #[should_panic(expected = "dimension 1")]
634 fn test_algebra_from_components_wrong_dim() {
635 let _ = RPlusAlgebra::from_components(&[1.0, 2.0]);
636 }
637
638 #[test]
639 fn test_algebra_norm() {
640 let x = RPlusAlgebra(-3.0);
641 assert!((x.norm() - 3.0).abs() < 1e-10);
642
643 let y = RPlusAlgebra(2.5);
644 assert!((y.norm() - 2.5).abs() < 1e-10);
645 }
646
647 #[test]
648 fn test_from_log() {
649 let g = RPlus::from_log(0.0);
651 assert!((g.value() - 1.0).abs() < 1e-10);
652
653 let h = RPlus::from_log(1.0);
655 assert!((h.value() - std::f64::consts::E).abs() < 1e-10);
656
657 let k = RPlus::from_log(-1.0);
659 assert!((k.value() - 1.0 / std::f64::consts::E).abs() < 1e-10);
660 }
661
662 #[test]
663 fn test_group_dim() {
664 assert_eq!(RPlus::dim(), 1);
665 }
666
667 #[test]
668 #[should_panic(expected = "positive")]
669 fn test_from_value_panics_on_zero() {
670 let _ = RPlus::from_value(0.0);
671 }
672
673 #[test]
674 #[should_panic(expected = "positive")]
675 fn test_from_value_panics_on_negative() {
676 let _ = RPlus::from_value(-1.0);
677 }
678}