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