1use std::fmt;
9use std::num::ParseFloatError;
10use std::ops::{
11 Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign,
12};
13use std::str::FromStr;
14
15use approx::{AbsDiffEq, RelativeEq, UlpsEq};
16use nalgebra::{DMatrix, DVector, Dyn, SMatrix, SVector, SVD};
17use num_traits::{FromPrimitive, Num, One, Signed, ToPrimitive, Zero};
18use simba::scalar::{ComplexField, Field, RealField, SubsetOf};
19use simba::simd::{PrimitiveSimdValue, SimdValue};
20use trust_region_least_squares::trf::{BackendError, HostNumerics};
21
22#[repr(transparent)]
24#[derive(Clone, Copy, Default, Debug, PartialEq)]
25pub struct Portable(pub f64);
26
27#[derive(Debug, Clone, Copy, Default)]
39pub(crate) struct PortableNumerics;
40
41impl HostNumerics for PortableNumerics {
42 fn svd(
43 &self,
44 values: &[f64],
45 rows: usize,
46 cols: usize,
47 ) -> Result<(Vec<f64>, Vec<f64>, Vec<f64>), BackendError> {
48 thin_svd(values, rows, cols).map_err(BackendError::Failed)
49 }
50
51 fn power(&self, values: &[f64], exponent: f64) -> Result<Option<Vec<f64>>, BackendError> {
52 Ok(Some(
53 values
54 .iter()
55 .copied()
56 .map(|value| libm::pow(value, exponent))
57 .collect(),
58 ))
59 }
60
61 fn power_scalar(&self, base: f64, exponent: f64) -> Result<Option<f64>, BackendError> {
62 Ok(Some(libm::pow(base, exponent)))
63 }
64
65 fn log1p(&self, value: f64) -> Result<Option<f64>, BackendError> {
66 Ok(Some(libm::log1p(value)))
67 }
68
69 fn atan(&self, value: f64) -> Result<Option<f64>, BackendError> {
70 Ok(Some(libm::atan(value)))
71 }
72}
73#[inline]
75pub fn matrix_from_row_slice(rows: usize, cols: usize, values: &[f64]) -> DMatrix<Portable> {
76 DMatrix::from_row_slice(
77 rows,
78 cols,
79 &values.iter().copied().map(Portable).collect::<Vec<_>>(),
80 )
81}
82
83#[inline]
85pub fn matrix_from_f64(matrix: &DMatrix<f64>) -> DMatrix<Portable> {
86 DMatrix::from_iterator(
87 matrix.nrows(),
88 matrix.ncols(),
89 matrix.iter().copied().map(Portable),
90 )
91}
92
93#[inline]
95pub fn matrix_to_f64(matrix: &DMatrix<Portable>) -> DMatrix<f64> {
96 DMatrix::from_iterator(
97 matrix.nrows(),
98 matrix.ncols(),
99 matrix.iter().map(|value| value.0),
100 )
101}
102
103#[inline]
105pub fn vector_from_f64(vector: &DVector<f64>) -> DVector<Portable> {
106 DVector::from_iterator(vector.len(), vector.iter().copied().map(Portable))
107}
108
109#[inline]
111pub fn vector_to_f64(vector: &DVector<Portable>) -> DVector<f64> {
112 DVector::from_iterator(vector.len(), vector.iter().map(|value| value.0))
113}
114
115#[allow(clippy::type_complexity)]
117pub fn thin_svd(
118 values: &[f64],
119 rows: usize,
120 cols: usize,
121) -> Result<(Vec<f64>, Vec<f64>, Vec<f64>), String> {
122 if values.len() != rows.saturating_mul(cols) {
123 return Err(format!(
124 "SVD input has length {}, expected {}x{}",
125 values.len(),
126 rows,
127 cols
128 ));
129 }
130 let matrix = matrix_from_row_slice(rows, cols, values);
131 let svd = matrix.svd(true, true);
132 let u = svd
133 .u
134 .ok_or_else(|| "portable SVD did not produce U".to_string())?;
135 let vt = svd
136 .v_t
137 .ok_or_else(|| "portable SVD did not produce V^T".to_string())?;
138 let k = rows.min(cols);
139 let mut u_out = vec![0.0; rows * k];
140 for row in 0..rows {
141 for col in 0..k {
142 u_out[row * k + col] = u[(row, col)].0;
143 }
144 }
145 let s_out = svd.singular_values.iter().map(|value| value.0).collect();
146 let mut vt_out = vec![0.0; k * cols];
147 for row in 0..k {
148 for col in 0..cols {
149 vt_out[row * cols + col] = vt[(row, col)].0;
150 }
151 }
152 Ok((u_out, s_out, vt_out))
153}
154
155#[inline]
157pub fn product(lhs: &DMatrix<f64>, rhs: &DMatrix<f64>) -> DMatrix<f64> {
158 matrix_to_f64(&(matrix_from_f64(lhs) * matrix_from_f64(rhs)))
159}
160
161#[inline]
163pub fn product_vector(lhs: &DMatrix<f64>, rhs: &DVector<f64>) -> DVector<f64> {
164 vector_to_f64(&(matrix_from_f64(lhs) * vector_from_f64(rhs)))
165}
166
167#[inline]
169pub fn product_fixed<const N: usize>(
170 lhs: &SMatrix<f64, N, N>,
171 rhs: &SMatrix<f64, N, N>,
172) -> SMatrix<f64, N, N> {
173 let lhs_portable = SMatrix::<Portable, N, N>::from_fn(|row, col| Portable(lhs[(row, col)]));
174 let rhs_portable = SMatrix::<Portable, N, N>::from_fn(|row, col| Portable(rhs[(row, col)]));
175 let product = lhs_portable * rhs_portable;
176 SMatrix::from_fn(|row, col| product[(row, col)].0)
177}
178
179#[inline]
182pub fn solve_lu(lhs: &DMatrix<f64>, rhs: &DVector<f64>) -> Option<DVector<f64>> {
183 matrix_from_f64(lhs)
184 .lu()
185 .solve(&vector_from_f64(rhs))
186 .map(|solution| vector_to_f64(&solution))
187}
188
189#[inline]
191pub fn solve_cholesky(lhs: &DMatrix<f64>, rhs: &DVector<f64>) -> Option<DVector<f64>> {
192 matrix_from_f64(lhs)
193 .cholesky()
194 .map(|factor| vector_to_f64(&factor.solve(&vector_from_f64(rhs))))
195}
196
197#[inline]
200pub fn cholesky_lower_dynamic(lhs: &DMatrix<f64>) -> Option<DMatrix<f64>> {
201 matrix_from_f64(lhs)
202 .cholesky()
203 .map(|factor| matrix_to_f64(&factor.l()))
204}
205
206#[inline]
208pub fn symmetric_eigen_dynamic(matrix: &DMatrix<f64>) -> (DMatrix<f64>, DVector<f64>) {
209 let eigen = matrix_from_f64(matrix).symmetric_eigen();
210 (
211 matrix_to_f64(&eigen.eigenvectors),
212 vector_to_f64(&eigen.eigenvalues),
213 )
214}
215
216#[inline]
219pub fn symmetric_eigen6(matrix: &SMatrix<f64, 6, 6>) -> (SMatrix<f64, 6, 6>, SVector<f64, 6>) {
220 let portable = SMatrix::<Portable, 6, 6>::from_fn(|row, col| Portable(matrix[(row, col)]));
221 let eigen = portable.symmetric_eigen();
222 (
223 SMatrix::from_fn(|row, col| eigen.eigenvectors[(row, col)].0),
224 SVector::from_fn(|row, _| eigen.eigenvalues[row].0),
225 )
226}
227
228#[inline]
230pub fn cholesky_lower<const N: usize>(matrix: &SMatrix<f64, N, N>) -> Option<SMatrix<f64, N, N>> {
231 let portable = SMatrix::<Portable, N, N>::from_fn(|row, col| Portable(matrix[(row, col)]));
232 portable
233 .cholesky()
234 .map(|factor| SMatrix::from_fn(|row, col| factor.l()[(row, col)].0))
235}
236
237#[inline]
239pub fn svd(matrix: &DMatrix<f64>, compute_u: bool, compute_v: bool) -> SVD<Portable, Dyn, Dyn> {
240 matrix_from_f64(matrix).svd(compute_u, compute_v)
241}
242
243impl Portable {
244 #[inline]
245 pub const fn new(value: f64) -> Self {
246 Self(value)
247 }
248
249 #[inline]
250 pub const fn get(self) -> f64 {
251 self.0
252 }
253}
254
255impl PartialOrd for Portable {
256 #[inline]
257 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
258 self.0.partial_cmp(&other.0)
259 }
260}
261
262impl fmt::Display for Portable {
263 #[inline]
264 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
265 self.0.fmt(formatter)
266 }
267}
268
269impl From<f64> for Portable {
270 #[inline]
271 fn from(value: f64) -> Self {
272 Self(value)
273 }
274}
275
276impl From<Portable> for f64 {
277 #[inline]
278 fn from(value: Portable) -> Self {
279 value.0
280 }
281}
282
283impl Add for Portable {
284 type Output = Self;
285
286 #[inline]
287 fn add(self, rhs: Self) -> Self {
288 Self(self.0 + rhs.0)
289 }
290}
291
292impl AddAssign for Portable {
293 #[inline]
294 fn add_assign(&mut self, rhs: Self) {
295 self.0 += rhs.0;
296 }
297}
298
299impl Sub for Portable {
300 type Output = Self;
301
302 #[inline]
303 fn sub(self, rhs: Self) -> Self {
304 Self(self.0 - rhs.0)
305 }
306}
307
308impl SubAssign for Portable {
309 #[inline]
310 fn sub_assign(&mut self, rhs: Self) {
311 self.0 -= rhs.0;
312 }
313}
314
315impl Mul for Portable {
316 type Output = Self;
317
318 #[inline]
319 fn mul(self, rhs: Self) -> Self {
320 Self(self.0 * rhs.0)
321 }
322}
323
324impl MulAssign for Portable {
325 #[inline]
326 fn mul_assign(&mut self, rhs: Self) {
327 self.0 *= rhs.0;
328 }
329}
330
331impl Div for Portable {
332 type Output = Self;
333
334 #[inline]
335 fn div(self, rhs: Self) -> Self {
336 Self(self.0 / rhs.0)
337 }
338}
339
340impl DivAssign for Portable {
341 #[inline]
342 fn div_assign(&mut self, rhs: Self) {
343 self.0 /= rhs.0;
344 }
345}
346
347impl Rem for Portable {
348 type Output = Self;
349
350 #[inline]
351 fn rem(self, rhs: Self) -> Self {
352 Self(self.0 % rhs.0)
353 }
354}
355
356impl RemAssign for Portable {
357 #[inline]
358 fn rem_assign(&mut self, rhs: Self) {
359 self.0 %= rhs.0;
360 }
361}
362
363impl Neg for Portable {
364 type Output = Self;
365
366 #[inline]
367 fn neg(self) -> Self {
368 Self(-self.0)
369 }
370}
371
372impl Zero for Portable {
373 #[inline]
374 fn zero() -> Self {
375 Self(0.0)
376 }
377
378 #[inline]
379 fn is_zero(&self) -> bool {
380 self.0 == 0.0
381 }
382}
383
384impl One for Portable {
385 #[inline]
386 fn one() -> Self {
387 Self(1.0)
388 }
389}
390
391impl Num for Portable {
392 type FromStrRadixErr = ParseFloatError;
393
394 #[inline]
395 fn from_str_radix(src: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> {
396 if radix == 10 {
397 src.parse().map(Self)
398 } else {
399 src.parse().map(Self)
402 }
403 }
404}
405
406impl Signed for Portable {
407 #[inline]
408 fn abs(&self) -> Self {
409 Self(libm::fabs(self.0))
410 }
411
412 #[inline]
413 fn abs_sub(&self, other: &Self) -> Self {
414 if self.0 <= other.0 {
415 Self::zero()
416 } else {
417 Self(self.0 - other.0)
418 }
419 }
420
421 #[inline]
422 fn signum(&self) -> Self {
423 Self(self.0.signum())
424 }
425
426 #[inline]
427 fn is_positive(&self) -> bool {
428 self.0 > 0.0
429 }
430
431 #[inline]
432 fn is_negative(&self) -> bool {
433 self.0 < 0.0
434 }
435}
436
437impl ToPrimitive for Portable {
438 #[inline]
439 fn to_i64(&self) -> Option<i64> {
440 self.0.to_i64()
441 }
442
443 #[inline]
444 fn to_u64(&self) -> Option<u64> {
445 self.0.to_u64()
446 }
447
448 #[inline]
449 fn to_f64(&self) -> Option<f64> {
450 Some(self.0)
451 }
452
453 #[inline]
454 fn to_f32(&self) -> Option<f32> {
455 Some(self.0 as f32)
456 }
457}
458
459impl FromPrimitive for Portable {
460 #[inline]
461 fn from_i64(value: i64) -> Option<Self> {
462 Some(Self(value as f64))
463 }
464
465 #[inline]
466 fn from_u64(value: u64) -> Option<Self> {
467 Some(Self(value as f64))
468 }
469
470 #[inline]
471 fn from_f64(value: f64) -> Option<Self> {
472 Some(Self(value))
473 }
474
475 #[inline]
476 fn from_f32(value: f32) -> Option<Self> {
477 Some(Self(value as f64))
478 }
479}
480
481impl SubsetOf<Portable> for f64 {
482 #[inline]
483 fn to_superset(&self) -> Portable {
484 Portable(*self)
485 }
486
487 #[inline]
488 fn from_superset_unchecked(element: &Portable) -> Self {
489 element.0
490 }
491
492 #[inline]
493 fn is_in_subset(_: &Portable) -> bool {
494 true
495 }
496}
497
498impl SubsetOf<Portable> for f32 {
499 #[inline]
500 fn to_superset(&self) -> Portable {
501 Portable(f64::from(*self))
502 }
503
504 #[inline]
505 fn from_superset_unchecked(element: &Portable) -> Self {
506 element.0 as f32
507 }
508
509 #[inline]
510 fn is_in_subset(_: &Portable) -> bool {
511 true
512 }
513}
514
515impl SubsetOf<f64> for Portable {
516 #[inline]
517 fn to_superset(&self) -> f64 {
518 self.0
519 }
520
521 #[inline]
522 fn from_superset_unchecked(element: &f64) -> Self {
523 Self(*element)
524 }
525
526 #[inline]
527 fn is_in_subset(_: &f64) -> bool {
528 true
529 }
530}
531
532impl SubsetOf<f32> for Portable {
533 #[inline]
534 fn to_superset(&self) -> f32 {
535 self.0 as f32
536 }
537
538 #[inline]
539 fn from_superset_unchecked(element: &f32) -> Self {
540 Self(f64::from(*element))
541 }
542
543 #[inline]
544 fn is_in_subset(_: &f32) -> bool {
545 true
546 }
547}
548
549impl SubsetOf<Portable> for Portable {
550 #[inline]
551 fn to_superset(&self) -> Self {
552 *self
553 }
554
555 #[inline]
556 fn from_superset_unchecked(element: &Self) -> Self {
557 *element
558 }
559
560 #[inline]
561 fn is_in_subset(_: &Self) -> bool {
562 true
563 }
564}
565
566impl Field for Portable {}
567
568impl SimdValue for Portable {
569 const LANES: usize = 1;
570 type Element = Self;
571 type SimdBool = bool;
572
573 #[inline]
574 fn splat(value: Self::Element) -> Self {
575 value
576 }
577
578 #[inline]
579 fn extract(&self, _: usize) -> Self::Element {
580 *self
581 }
582
583 #[inline]
584 unsafe fn extract_unchecked(&self, _: usize) -> Self::Element {
585 *self
586 }
587
588 #[inline]
589 fn replace(&mut self, _: usize, value: Self::Element) {
590 *self = value;
591 }
592
593 #[inline]
594 unsafe fn replace_unchecked(&mut self, _: usize, value: Self::Element) {
595 *self = value;
596 }
597
598 #[inline]
599 fn select(self, condition: bool, other: Self) -> Self {
600 if condition {
601 self
602 } else {
603 other
604 }
605 }
606}
607
608impl PrimitiveSimdValue for Portable {}
609
610impl AbsDiffEq for Portable {
611 type Epsilon = Self;
612
613 #[inline]
614 fn default_epsilon() -> Self::Epsilon {
615 Self(f64::EPSILON)
616 }
617
618 #[inline]
619 fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
620 libm::fabs(self.0 - other.0) <= epsilon.0
621 }
622
623 #[inline]
624 fn abs_diff_ne(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
625 !self.abs_diff_eq(other, epsilon)
626 }
627}
628
629impl RelativeEq for Portable {
630 #[inline]
631 fn default_max_relative() -> Self::Epsilon {
632 Self(f64::EPSILON)
633 }
634
635 #[inline]
636 fn relative_eq(
637 &self,
638 other: &Self,
639 epsilon: Self::Epsilon,
640 max_relative: Self::Epsilon,
641 ) -> bool {
642 if self.abs_diff_eq(other, epsilon) {
643 true
644 } else {
645 libm::fabs(self.0 - other.0) <= max_relative.0 * self.0.abs().max(other.0.abs())
646 }
647 }
648
649 #[inline]
650 fn relative_ne(
651 &self,
652 other: &Self,
653 epsilon: Self::Epsilon,
654 max_relative: Self::Epsilon,
655 ) -> bool {
656 !self.relative_eq(other, epsilon, max_relative)
657 }
658}
659
660impl UlpsEq for Portable {
661 #[inline]
662 fn default_max_ulps() -> u32 {
663 4
664 }
665
666 #[inline]
667 fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool {
668 self.0.to_bits().abs_diff(other.0.to_bits()) <= u64::from(max_ulps)
669 || self.abs_diff_eq(other, epsilon)
670 }
671
672 #[inline]
673 fn ulps_ne(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool {
674 !self.ulps_eq(other, epsilon, max_ulps)
675 }
676}
677
678impl ComplexField for Portable {
679 type RealField = Self;
680
681 #[inline]
682 fn from_real(re: Self) -> Self {
683 re
684 }
685
686 #[inline]
687 fn real(self) -> Self {
688 self
689 }
690
691 #[inline]
692 fn imaginary(self) -> Self {
693 Self::zero()
694 }
695
696 #[inline]
697 fn norm1(self) -> Self {
698 self.abs()
699 }
700
701 #[inline]
702 fn modulus(self) -> Self {
703 self.abs()
704 }
705
706 #[inline]
707 fn modulus_squared(self) -> Self {
708 self * self
709 }
710
711 #[inline]
712 fn argument(self) -> Self {
713 if self >= Self::zero() {
714 Self::zero()
715 } else {
716 Self::pi()
717 }
718 }
719
720 #[inline]
721 fn to_exp(self) -> (Self, Self) {
722 if self >= Self::zero() {
723 (self, Self::one())
724 } else {
725 (-self, -Self::one())
726 }
727 }
728
729 #[inline]
730 fn recip(self) -> Self {
731 Self(self.0.recip())
732 }
733
734 #[inline]
735 fn conjugate(self) -> Self {
736 self
737 }
738
739 #[inline]
740 fn scale(self, factor: Self) -> Self {
741 self * factor
742 }
743
744 #[inline]
745 fn unscale(self, factor: Self) -> Self {
746 self / factor
747 }
748
749 #[inline]
750 fn floor(self) -> Self {
751 Self(libm::floor(self.0))
752 }
753
754 #[inline]
755 fn ceil(self) -> Self {
756 Self(libm::ceil(self.0))
757 }
758
759 #[inline]
760 fn round(self) -> Self {
761 Self(libm::round(self.0))
762 }
763
764 #[inline]
765 fn trunc(self) -> Self {
766 Self(libm::trunc(self.0))
767 }
768
769 #[inline]
770 fn fract(self) -> Self {
771 Self(self.0 - libm::trunc(self.0))
772 }
773
774 #[inline]
775 fn mul_add(self, a: Self, b: Self) -> Self {
776 Self(libm::fma(self.0, a.0, b.0))
777 }
778
779 #[inline]
780 fn abs(self) -> Self {
781 Self(libm::fabs(self.0))
782 }
783
784 #[inline]
785 fn hypot(self, other: Self) -> Self {
786 Self(libm::hypot(self.0, other.0))
787 }
788
789 #[inline]
790 fn powi(self, exponent: i32) -> Self {
791 if exponent == 0 {
792 return Self::one();
793 }
794 let negative = exponent < 0;
795 let mut power = exponent.unsigned_abs();
796 let mut base = self;
797 let mut result = Self::one();
798 while power != 0 {
799 if power & 1 != 0 {
800 result *= base;
801 }
802 base *= base;
803 power >>= 1;
804 }
805 if negative {
806 Self::one() / result
807 } else {
808 result
809 }
810 }
811
812 #[inline]
813 fn powf(self, exponent: Self) -> Self {
814 Self(libm::pow(self.0, exponent.0))
815 }
816
817 #[inline]
818 fn powc(self, exponent: Self) -> Self {
819 self.powf(exponent)
820 }
821
822 #[inline]
823 fn sqrt(self) -> Self {
824 Self(libm::sqrt(self.0))
825 }
826
827 #[inline]
828 fn try_sqrt(self) -> Option<Self> {
829 if self >= Self::zero() {
830 Some(self.sqrt())
831 } else {
832 None
833 }
834 }
835
836 #[inline]
837 fn exp(self) -> Self {
838 Self(libm::exp(self.0))
839 }
840
841 #[inline]
842 fn exp2(self) -> Self {
843 Self(libm::exp2(self.0))
844 }
845
846 #[inline]
847 fn exp_m1(self) -> Self {
848 Self(libm::expm1(self.0))
849 }
850
851 #[inline]
852 fn ln_1p(self) -> Self {
853 Self(libm::log1p(self.0))
854 }
855
856 #[inline]
857 fn ln(self) -> Self {
858 Self(libm::log(self.0))
859 }
860
861 #[inline]
862 fn log(self, base: Self) -> Self {
863 Self(libm::log(self.0) / libm::log(base.0))
864 }
865
866 #[inline]
867 fn log2(self) -> Self {
868 Self(libm::log2(self.0))
869 }
870
871 #[inline]
872 fn log10(self) -> Self {
873 Self(libm::log10(self.0))
874 }
875
876 #[inline]
877 fn cbrt(self) -> Self {
878 Self(libm::cbrt(self.0))
879 }
880
881 #[inline]
882 fn sin(self) -> Self {
883 Self(libm::sin(self.0))
884 }
885
886 #[inline]
887 fn cos(self) -> Self {
888 Self(libm::cos(self.0))
889 }
890
891 #[inline]
892 fn sin_cos(self) -> (Self, Self) {
893 let (sin, cos) = libm::sincos(self.0);
894 (Self(sin), Self(cos))
895 }
896
897 #[inline]
898 fn tan(self) -> Self {
899 Self(libm::tan(self.0))
900 }
901
902 #[inline]
903 fn asin(self) -> Self {
904 Self(libm::asin(self.0))
905 }
906
907 #[inline]
908 fn acos(self) -> Self {
909 Self(libm::acos(self.0))
910 }
911
912 #[inline]
913 fn atan(self) -> Self {
914 Self(libm::atan(self.0))
915 }
916
917 #[inline]
918 fn sinh(self) -> Self {
919 Self(libm::sinh(self.0))
920 }
921
922 #[inline]
923 fn cosh(self) -> Self {
924 Self(libm::cosh(self.0))
925 }
926
927 #[inline]
928 fn tanh(self) -> Self {
929 Self(libm::tanh(self.0))
930 }
931
932 #[inline]
933 fn asinh(self) -> Self {
934 Self(libm::asinh(self.0))
935 }
936
937 #[inline]
938 fn acosh(self) -> Self {
939 Self(libm::acosh(self.0))
940 }
941
942 #[inline]
943 fn atanh(self) -> Self {
944 Self(libm::atanh(self.0))
945 }
946
947 #[inline]
948 fn is_finite(&self) -> bool {
949 self.0.is_finite()
950 }
951}
952
953impl RealField for Portable {
954 #[inline]
955 fn is_sign_positive(&self) -> bool {
956 self.0.is_sign_positive()
957 }
958
959 #[inline]
960 fn is_sign_negative(&self) -> bool {
961 self.0.is_sign_negative()
962 }
963
964 #[inline]
965 fn copysign(self, sign: Self) -> Self {
966 Self(libm::copysign(self.0, sign.0))
967 }
968
969 #[inline]
970 fn max(self, other: Self) -> Self {
971 Self(self.0.max(other.0))
972 }
973
974 #[inline]
975 fn min(self, other: Self) -> Self {
976 Self(self.0.min(other.0))
977 }
978
979 #[inline]
980 fn clamp(self, min: Self, max: Self) -> Self {
981 Self(self.0.clamp(min.0, max.0))
982 }
983
984 #[inline]
985 fn atan2(self, other: Self) -> Self {
986 Self(libm::atan2(self.0, other.0))
987 }
988
989 #[inline]
990 fn min_value() -> Option<Self> {
991 Some(Self(f64::MIN))
992 }
993
994 #[inline]
995 fn max_value() -> Option<Self> {
996 Some(Self(f64::MAX))
997 }
998
999 #[inline]
1000 fn pi() -> Self {
1001 Self(std::f64::consts::PI)
1002 }
1003
1004 #[inline]
1005 fn two_pi() -> Self {
1006 Self(std::f64::consts::PI + std::f64::consts::PI)
1007 }
1008
1009 #[inline]
1010 fn frac_pi_2() -> Self {
1011 Self(std::f64::consts::FRAC_PI_2)
1012 }
1013
1014 #[inline]
1015 fn frac_pi_3() -> Self {
1016 Self(std::f64::consts::FRAC_PI_3)
1017 }
1018
1019 #[inline]
1020 fn frac_pi_4() -> Self {
1021 Self(std::f64::consts::FRAC_PI_4)
1022 }
1023
1024 #[inline]
1025 fn frac_pi_6() -> Self {
1026 Self(std::f64::consts::FRAC_PI_6)
1027 }
1028
1029 #[inline]
1030 fn frac_pi_8() -> Self {
1031 Self(std::f64::consts::FRAC_PI_8)
1032 }
1033
1034 #[inline]
1035 fn frac_1_pi() -> Self {
1036 Self(std::f64::consts::FRAC_1_PI)
1037 }
1038
1039 #[inline]
1040 fn frac_2_pi() -> Self {
1041 Self(std::f64::consts::FRAC_2_PI)
1042 }
1043
1044 #[inline]
1045 fn frac_2_sqrt_pi() -> Self {
1046 Self(std::f64::consts::FRAC_2_SQRT_PI)
1047 }
1048
1049 #[inline]
1050 fn e() -> Self {
1051 Self(std::f64::consts::E)
1052 }
1053
1054 #[inline]
1055 fn log2_e() -> Self {
1056 Self(std::f64::consts::LOG2_E)
1057 }
1058
1059 #[inline]
1060 fn log10_e() -> Self {
1061 Self(std::f64::consts::LOG10_E)
1062 }
1063
1064 #[inline]
1065 fn ln_2() -> Self {
1066 Self(std::f64::consts::LN_2)
1067 }
1068
1069 #[inline]
1070 fn ln_10() -> Self {
1071 Self(std::f64::consts::LN_10)
1072 }
1073}
1074
1075impl FromStr for Portable {
1076 type Err = ParseFloatError;
1077
1078 #[inline]
1079 fn from_str(value: &str) -> Result<Self, Self::Err> {
1080 value.parse().map(Self)
1081 }
1082}
1083
1084#[cfg(test)]
1085mod tests {
1086 use super::*;
1087 use std::cell::RefCell;
1088
1089 #[derive(Default)]
1090 struct LossHookProbe {
1091 log1p_inputs: RefCell<Vec<f64>>,
1092 atan_inputs: RefCell<Vec<f64>>,
1093 }
1094
1095 impl HostNumerics for LossHookProbe {
1096 fn svd(
1097 &self,
1098 values: &[f64],
1099 rows: usize,
1100 cols: usize,
1101 ) -> Result<(Vec<f64>, Vec<f64>, Vec<f64>), BackendError> {
1102 PortableNumerics.svd(values, rows, cols)
1103 }
1104
1105 fn log1p(&self, value: f64) -> Result<Option<f64>, BackendError> {
1106 self.log1p_inputs.borrow_mut().push(value);
1107 Ok(Some(17.0 + value))
1108 }
1109
1110 fn atan(&self, value: f64) -> Result<Option<f64>, BackendError> {
1111 self.atan_inputs.borrow_mut().push(value);
1112 Ok(Some(23.0 + value))
1113 }
1114 }
1115
1116 fn samples(count: usize) -> Vec<f64> {
1117 let mut state = 0x9e3779b97f4a7c15_u64;
1118 (0..count)
1119 .map(|_| {
1120 state = state
1121 .wrapping_mul(0xd1342543de82ef95)
1122 .wrapping_add(0xa4093822299f31d0);
1123 let fraction = f64::from_bits(0x3ff0000000000000 | (state >> 12)) - 1.0;
1124 let signed = if state & 1 == 0 { fraction } else { -fraction };
1125 signed * 64.0
1126 })
1127 .collect()
1128 }
1129
1130 fn repeated_square(value: f64, exponent: i32) -> f64 {
1131 let negative = exponent < 0;
1132 let mut power = exponent.unsigned_abs();
1133 let mut base = value;
1134 let mut result = 1.0;
1135 while power != 0 {
1136 if power & 1 != 0 {
1137 result *= base;
1138 }
1139 base *= base;
1140 power >>= 1;
1141 }
1142 if negative {
1143 1.0 / result
1144 } else {
1145 result
1146 }
1147 }
1148
1149 #[test]
1150 fn arithmetic_is_a_bit_copy_of_binary64() {
1151 let mut values = samples(128);
1152 values.extend([0.0, -0.0, 1.25, -3.5, f64::MIN_POSITIVE, f64::MAX]);
1153 for &left in &values {
1154 for &right in &values {
1155 assert_eq!(
1156 (Portable(left) + Portable(right)).0.to_bits(),
1157 (left + right).to_bits()
1158 );
1159 assert_eq!(
1160 (Portable(left) - Portable(right)).0.to_bits(),
1161 (left - right).to_bits()
1162 );
1163 assert_eq!(
1164 (Portable(left) * Portable(right)).0.to_bits(),
1165 (left * right).to_bits()
1166 );
1167 assert_eq!(
1168 (Portable(left) / Portable(right)).0.to_bits(),
1169 (left / right).to_bits()
1170 );
1171 assert_eq!(
1172 Portable(left).partial_cmp(&Portable(right)),
1173 left.partial_cmp(&right)
1174 );
1175 assert_eq!(Portable(left) < Portable(right), left < right);
1176 assert_eq!(Portable(left) <= Portable(right), left <= right);
1177 assert_eq!(Portable(left) > Portable(right), left > right);
1178 assert_eq!(Portable(left) >= Portable(right), left >= right);
1179 }
1180 }
1181 }
1182
1183 #[test]
1184 fn transcendental_delegates_match_libm() {
1185 for value in samples(128) {
1186 let portable = Portable(value);
1187 assert_eq!(portable.sin().0.to_bits(), libm::sin(value).to_bits());
1188 assert_eq!(portable.cos().0.to_bits(), libm::cos(value).to_bits());
1189 let (sin, cos) = portable.sin_cos();
1190 let (expected_sin, expected_cos) = libm::sincos(value);
1191 assert_eq!(sin.0.to_bits(), expected_sin.to_bits());
1192 assert_eq!(cos.0.to_bits(), expected_cos.to_bits());
1193 assert_eq!(portable.tan().0.to_bits(), libm::tan(value).to_bits());
1194 assert_eq!(portable.sinh().0.to_bits(), libm::sinh(value).to_bits());
1195 assert_eq!(portable.cosh().0.to_bits(), libm::cosh(value).to_bits());
1196 assert_eq!(portable.tanh().0.to_bits(), libm::tanh(value).to_bits());
1197 assert_eq!(portable.asinh().0.to_bits(), libm::asinh(value).to_bits());
1198 assert_eq!(portable.cbrt().0.to_bits(), libm::cbrt(value).to_bits());
1199 assert_eq!(
1200 portable.hypot(portable).0.to_bits(),
1201 libm::hypot(value, value).to_bits()
1202 );
1203 assert_eq!(portable.sqrt().0.to_bits(), libm::sqrt(value).to_bits());
1204 assert_eq!(portable.exp().0.to_bits(), libm::exp(value).to_bits());
1205 assert_eq!(portable.exp2().0.to_bits(), libm::exp2(value).to_bits());
1206 assert_eq!(portable.exp_m1().0.to_bits(), libm::expm1(value).to_bits());
1207 assert_eq!(portable.atan().0.to_bits(), libm::atan(value).to_bits());
1208 assert_eq!(
1209 portable.atan2(portable).0.to_bits(),
1210 libm::atan2(value, value).to_bits()
1211 );
1212 let positive = value.abs() + 0.25;
1213 assert_eq!(
1214 Portable(positive).ln().0.to_bits(),
1215 libm::log(positive).to_bits()
1216 );
1217 assert_eq!(
1218 Portable(positive).log2().0.to_bits(),
1219 libm::log2(positive).to_bits()
1220 );
1221 assert_eq!(
1222 Portable(positive).log10().0.to_bits(),
1223 libm::log10(positive).to_bits()
1224 );
1225 assert_eq!(
1226 Portable(value / 64.0).asin().0.to_bits(),
1227 libm::asin(value / 64.0).to_bits()
1228 );
1229 assert_eq!(
1230 Portable(value / 64.0).acos().0.to_bits(),
1231 libm::acos(value / 64.0).to_bits()
1232 );
1233 assert_eq!(
1234 Portable(value / 64.0).atanh().0.to_bits(),
1235 libm::atanh(value / 64.0).to_bits()
1236 );
1237 assert_eq!(
1238 Portable(value / 64.0 + 0.5).ln_1p().0.to_bits(),
1239 libm::log1p(value / 64.0 + 0.5).to_bits()
1240 );
1241 assert_eq!(
1242 Portable(value / 64.0 + 1.0).acosh().0.to_bits(),
1243 libm::acosh(value / 64.0 + 1.0).to_bits()
1244 );
1245 let exponent = (value as i32) % 8;
1246 assert_eq!(
1247 Portable(value).powi(exponent).0.to_bits(),
1248 repeated_square(value, exponent).to_bits()
1249 );
1250 assert_eq!(
1251 Portable(positive).powf(Portable(value / 8.0)).0.to_bits(),
1252 libm::pow(positive, value / 8.0).to_bits()
1253 );
1254 assert_eq!(
1255 Portable(positive).log(Portable(positive + 0.5)).0.to_bits(),
1256 (libm::log(positive) / libm::log(positive + 0.5)).to_bits()
1257 );
1258 }
1259 }
1260
1261 #[test]
1262 fn portable_loss_backend_hooks_are_consulted() {
1263 use trust_region_least_squares::loss::{Loss, LossFunction};
1264
1265 let probe = LossHookProbe::default();
1266 let cauchy = LossFunction::new(Loss::Cauchy, 1.0)
1267 .evaluate_with(&[0.5], &probe)
1268 .expect("Cauchy hook");
1269 assert_eq!(probe.log1p_inputs.borrow().as_slice(), &[0.25]);
1270 assert_eq!(cauchy.rho0, vec![17.25]);
1271
1272 let arctan = LossFunction::new(Loss::Arctan, 1.0)
1273 .evaluate_with(&[0.5], &probe)
1274 .expect("Arctan hook");
1275 assert_eq!(probe.atan_inputs.borrow().as_slice(), &[0.25]);
1276 assert_eq!(arctan.rho0, vec![23.25]);
1277
1278 assert_eq!(
1279 PortableNumerics
1280 .log1p(0.25)
1281 .expect("portable log1p")
1282 .expect("supplied log1p")
1283 .to_bits(),
1284 libm::log1p(0.25).to_bits()
1285 );
1286 assert_eq!(
1287 PortableNumerics
1288 .atan(0.25)
1289 .expect("portable atan")
1290 .expect("supplied atan")
1291 .to_bits(),
1292 libm::atan(0.25).to_bits()
1293 );
1294 }
1295
1296 #[test]
1297 fn portable_fma_matches_libm() {
1298 let values = samples(128);
1299 for (index, &left) in values.iter().enumerate() {
1300 let right = values[(index * 37) % values.len()];
1301 let addend = values[(index * 71 + 11) % values.len()];
1302 assert_eq!(
1303 Portable(left)
1304 .mul_add(Portable(right), Portable(addend))
1305 .0
1306 .to_bits(),
1307 libm::fma(left, right, addend).to_bits()
1308 );
1309 }
1310 }
1311}