1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
//! Custom numeric traits for high-precision computation
//!
//! This module provides custom numeric traits that work with both f64 and the xprec Df64 backend
//! for high-precision numerical computation in gauss quadrature and matrix operations.
use crate::Df64;
use libm::expm1;
use simba::scalar::ComplexField;
use std::fmt::Debug;
/// Custom numeric trait for high-precision numerical computation
///
/// This trait provides the essential numeric operations needed for gauss module
/// and matrix_from_gauss functions. Supports both f64 and Df64 types.
///
/// Uses standard traits for common operations:
/// - num_traits::Zero for zero()
/// - simba::scalar::ComplexField for mathematical functions (abs, sqrt, cos, sin, exp, etc.)
/// - ComplexField::is_finite() for is_finite()
pub trait CustomNumeric:
Copy
+ Debug
+ PartialOrd
+ std::fmt::Display
+ std::ops::Add<Output = Self>
+ std::ops::Sub<Output = Self>
+ std::ops::Mul<Output = Self>
+ std::ops::Div<Output = Self>
+ std::ops::Neg<Output = Self>
+ num_traits::Zero // zero()
+ simba::scalar::ComplexField // abs, cos, sin, exp, sqrt, etc.
{
/// Convert from f64 to Self (direct conversion, no Option)
///
/// This is a static method that can be called as T::from_f64_unchecked(x)
/// Renamed to avoid conflict with num_traits::FromPrimitive::from_f64
fn from_f64_unchecked(x: f64) -> Self;
/// Convert from any CustomNumeric type to Self (generic conversion)
///
/// This allows conversion between different numeric types while preserving precision.
/// Can be called as T::convert_from(other_numeric_value)
fn convert_from<U: CustomNumeric + 'static>(value: U) -> Self;
/// Convert to f64
fn to_f64(self) -> f64;
/// Get machine epsilon
fn epsilon() -> Self;
/// Get high-precision PI constant
fn pi() -> Self;
/// Maximum of two values (not provided by ComplexField)
fn max(self, other: Self) -> Self;
/// Minimum of two values (not provided by ComplexField)
fn min(self, other: Self) -> Self;
/// Check if the value is valid (not NaN/infinite)
fn is_valid(&self) -> bool;
/// Check if the value is finite (not NaN or infinite)
/// Get absolute value as the same type (convenience method)
///
/// This method wraps ComplexField::abs() and converts the result back to Self
/// to avoid type conversion issues in generic code.
/// Note: Only abs() has this problem - other math functions (exp, cos, sin, sqrt)
/// already return Self directly from ComplexField.
fn abs_as_same_type(self) -> Self;
/// Compute exp(self) - 1 with higher precision for small values
///
/// This is more accurate than `self.exp() - 1` when self is close to zero,
/// avoiding catastrophic cancellation.
fn exp_m1(self) -> Self;
}
/// f64 implementation of CustomNumeric
impl CustomNumeric for f64 {
fn from_f64_unchecked(x: f64) -> Self {
x
}
fn convert_from<U: CustomNumeric + 'static>(value: U) -> Self {
// Use match to optimize conversion based on the source type
// Note: Using TypeId for compile-time optimization, but falling back to safe conversion
match std::any::TypeId::of::<U>() {
// For f64 to f64, this is just a copy (no conversion needed)
id if id == std::any::TypeId::of::<f64>() => {
// Safe: f64 to f64 conversion
// This is a no-op for f64
value.to_f64()
}
// For Df64 to f64, use the conversion method
id if id == std::any::TypeId::of::<Df64>() => {
// Safe: Df64 to f64 conversion
value.to_f64()
}
// Fallback: convert via f64 for unknown types
_ => value.to_f64(),
}
}
fn to_f64(self) -> f64 {
self
}
fn epsilon() -> Self {
f64::EPSILON
}
fn pi() -> Self {
std::f64::consts::PI
}
fn max(self, other: Self) -> Self {
self.max(other)
}
fn min(self, other: Self) -> Self {
self.min(other)
}
fn is_valid(&self) -> bool {
num_traits::Float::is_finite(*self)
}
fn abs_as_same_type(self) -> Self {
Self::convert_from(self.abs())
}
fn exp_m1(self) -> Self {
expm1(self)
}
}
/// Df64 implementation of CustomNumeric
impl CustomNumeric for Df64 {
fn from_f64_unchecked(x: f64) -> Self {
Df64::from(x)
}
fn convert_from<U: CustomNumeric + 'static>(value: U) -> Self {
// Use match to optimize conversion based on the source type
// Note: Using TypeId for compile-time optimization, but falling back to safe conversion
match std::any::TypeId::of::<U>() {
// For f64 to Df64, use the conversion method
id if id == std::any::TypeId::of::<f64>() => {
// Safe: f64 to Df64 conversion
let f64_value = value.to_f64();
Self::from_f64_unchecked(f64_value)
}
// For Df64 to Df64, this is just a copy (no conversion needed)
id if id == std::any::TypeId::of::<Df64>() => {
// Safe: Df64 to Df64 conversion (copy)
// We know U is Df64 from TypeId check, and Df64 implements Copy
// so we can safely copy the value without losing precision
// Note: We can't just return `value` directly because Rust's type system
// doesn't know that U == Df64 at compile time, even though we've verified
// it at runtime with TypeId. So we need unsafe transmute_copy.
unsafe {
// Safety: We've verified via TypeId that U == Df64, and Df64 is Copy
// so this is a no-op copy that preserves all precision (hi and lo parts)
std::mem::transmute_copy(&value)
}
}
// Fallback: convert via f64 for unknown types
_ => Self::from_f64_unchecked(value.to_f64()),
}
}
fn to_f64(self) -> f64 {
// Use hi() and lo() methods for better precision
let hi = self.hi();
let lo = self.lo();
hi + lo
}
fn epsilon() -> Self {
// Df64::EPSILON = f64::EPSILON * f64::EPSILON / 2.0
let epsilon_value = f64::EPSILON * f64::EPSILON / 2.0;
Df64::from(epsilon_value)
}
fn pi() -> Self {
Df64::from(std::f64::consts::PI)
}
fn max(self, other: Self) -> Self {
if self > other { self } else { other }
}
fn min(self, other: Self) -> Self {
if self < other { self } else { other }
}
fn is_valid(&self) -> bool {
let value = *self;
f64::from(value).is_finite() && !f64::from(value).is_nan()
}
fn abs_as_same_type(self) -> Self {
Self::convert_from(self.abs())
}
fn exp_m1(self) -> Self {
// Use ComplexField::exp_m1() for Df64
ComplexField::exp_m1(self)
}
}
// Note: ScalarOperand implementations for f64 and Df64 are provided by ndarray
// We cannot implement them here due to Orphan Rules, but they are already implemented
// in the ndarray crate for standard numeric types.
// Note: Df64ArrayOps trait and impl removed after ndarray migration
// Array operations should now be done using mdarray Tensor methods directly
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_f64_custom_numeric() {
let x = 1.5_f64;
let y = -2.0_f64;
// Test basic operations
assert_eq!(x.abs(), 1.5);
assert_eq!(y.abs(), 2.0);
// Test mathematical functions
let cos_x = x.cos();
assert!(f64::from(cos_x).is_finite());
let sqrt_x = x.sqrt();
assert!(f64::from(sqrt_x).is_finite());
// Test conversion
assert_eq!(x.to_f64(), 1.5);
assert_eq!(<f64 as CustomNumeric>::epsilon(), f64::EPSILON);
}
#[test]
fn test_twofloat_custom_numeric() {
let x = Df64::from_f64_unchecked(1.5);
let y = Df64::from_f64_unchecked(-2.0);
// Test basic operations
assert_eq!(x.abs(), Df64::from_f64_unchecked(1.5));
assert_eq!(y.abs(), Df64::from_f64_unchecked(2.0));
// Test mathematical functions
let cos_x = x.cos();
assert!(f64::from(cos_x).is_finite());
let sqrt_x = x.sqrt();
assert!(f64::from(sqrt_x).is_finite());
// Test conversion
let x_f64 = x.to_f64();
assert!((x_f64 - 1.5).abs() < 1e-15);
// Test epsilon
let eps = Df64::epsilon();
assert!(eps > Df64::from_f64_unchecked(0.0));
assert!(eps < Df64::from_f64_unchecked(1.0));
}
// Note: Df64ArrayOps tests removed after ndarray migration
#[test]
fn test_precision_comparison() {
// Test that Df64 provides higher precision than f64
let pi_f64 = std::f64::consts::PI;
let pi_tf = Df64::from_f64_unchecked(pi_f64);
// Both should be finite
assert!(pi_f64.is_finite());
assert!(f64::from(pi_tf).is_finite());
// Df64 should convert back to f64 with minimal loss
let pi_back = pi_tf.to_f64();
assert!((pi_back - pi_f64).abs() < f64::EPSILON);
}
}