uncertain_rs/operations/
arithmetic.rs

1use crate::traits::Shareable;
2use crate::{Uncertain, computation::ComputationNode};
3use std::ops::{Add, Div, Mul, Neg, Sub};
4
5/// Trait alias for types that support arithmetic operations
6pub trait Arithmetic:
7    Add<Output = Self>
8    + Sub<Output = Self>
9    + Mul<Output = Self>
10    + Div<Output = Self>
11    + Clone
12    + Send
13    + Sync
14    + 'static
15{
16    /// Returns the additive identity (zero)
17    fn zero() -> Self;
18
19    /// Returns the multiplicative identity (one)
20    fn one() -> Self;
21}
22
23// No blanket implementation to avoid conflicts with specific implementations
24
25// Implement Arithmetic for common numeric types
26impl Arithmetic for f64 {
27    fn zero() -> Self {
28        0.0
29    }
30
31    fn one() -> Self {
32        1.0
33    }
34}
35
36impl Arithmetic for f32 {
37    fn zero() -> Self {
38        0.0
39    }
40
41    fn one() -> Self {
42        1.0
43    }
44}
45
46impl Arithmetic for i32 {
47    fn zero() -> Self {
48        0
49    }
50
51    fn one() -> Self {
52        1
53    }
54}
55
56impl Arithmetic for i64 {
57    fn zero() -> Self {
58        0
59    }
60
61    fn one() -> Self {
62        1
63    }
64}
65
66impl Arithmetic for u32 {
67    fn zero() -> Self {
68        0
69    }
70
71    fn one() -> Self {
72        1
73    }
74}
75
76impl Arithmetic for u64 {
77    fn zero() -> Self {
78        0
79    }
80
81    fn one() -> Self {
82        1
83    }
84}
85
86/// Binary operation types for computation graph
87#[derive(Clone, Hash, PartialEq, Eq)]
88pub enum BinaryOperation {
89    Add,
90    Sub,
91    Mul,
92    Div,
93}
94
95impl BinaryOperation {
96    #[must_use]
97    pub fn apply<T>(&self, left: T, right: T) -> T
98    where
99        T: Arithmetic,
100    {
101        match self {
102            BinaryOperation::Add => left + right,
103            BinaryOperation::Sub => left - right,
104            BinaryOperation::Mul => left * right,
105            BinaryOperation::Div => left / right,
106        }
107    }
108}
109
110// Addition operations
111impl<T> Add for Uncertain<T>
112where
113    T: Arithmetic,
114{
115    type Output = Uncertain<T>;
116
117    fn add(self, rhs: Self) -> Self::Output {
118        let node = ComputationNode::BinaryOp {
119            left: Box::new(self.node),
120            right: Box::new(rhs.node),
121            operation: BinaryOperation::Add,
122        };
123        Uncertain::with_node(node)
124    }
125}
126
127impl<T> Add<T> for Uncertain<T>
128where
129    T: Arithmetic,
130{
131    type Output = Uncertain<T>;
132
133    fn add(self, rhs: T) -> Self::Output {
134        self + Uncertain::point(rhs)
135    }
136}
137
138impl Add<Uncertain<f64>> for f64 {
139    type Output = Uncertain<f64>;
140
141    fn add(self, rhs: Uncertain<f64>) -> Self::Output {
142        Uncertain::point(self) + rhs
143    }
144}
145
146// Subtraction operations
147impl<T> Sub for Uncertain<T>
148where
149    T: Arithmetic,
150{
151    type Output = Uncertain<T>;
152
153    fn sub(self, rhs: Self) -> Self::Output {
154        let node = ComputationNode::BinaryOp {
155            left: Box::new(self.node),
156            right: Box::new(rhs.node),
157            operation: BinaryOperation::Sub,
158        };
159        Uncertain::with_node(node)
160    }
161}
162
163impl<T> Sub<T> for Uncertain<T>
164where
165    T: Arithmetic,
166{
167    type Output = Uncertain<T>;
168
169    fn sub(self, rhs: T) -> Self::Output {
170        self - Uncertain::point(rhs)
171    }
172}
173
174impl Sub<Uncertain<f64>> for f64 {
175    type Output = Uncertain<f64>;
176
177    fn sub(self, rhs: Uncertain<f64>) -> Self::Output {
178        Uncertain::point(self) - rhs
179    }
180}
181
182// Multiplication operations
183impl<T> Mul for Uncertain<T>
184where
185    T: Arithmetic,
186{
187    type Output = Uncertain<T>;
188
189    fn mul(self, rhs: Self) -> Self::Output {
190        let node = ComputationNode::BinaryOp {
191            left: Box::new(self.node),
192            right: Box::new(rhs.node),
193            operation: BinaryOperation::Mul,
194        };
195        Uncertain::with_node(node)
196    }
197}
198
199impl<T> Mul<T> for Uncertain<T>
200where
201    T: Arithmetic,
202{
203    type Output = Uncertain<T>;
204
205    fn mul(self, rhs: T) -> Self::Output {
206        self * Uncertain::point(rhs)
207    }
208}
209
210impl Mul<Uncertain<f64>> for f64 {
211    type Output = Uncertain<f64>;
212
213    fn mul(self, rhs: Uncertain<f64>) -> Self::Output {
214        Uncertain::point(self) * rhs
215    }
216}
217
218// Division operations
219impl<T> Div for Uncertain<T>
220where
221    T: Arithmetic,
222{
223    type Output = Uncertain<T>;
224
225    fn div(self, rhs: Self) -> Self::Output {
226        let node = ComputationNode::BinaryOp {
227            left: Box::new(self.node),
228            right: Box::new(rhs.node),
229            operation: BinaryOperation::Div,
230        };
231        Uncertain::with_node(node)
232    }
233}
234
235impl<T> Div<T> for Uncertain<T>
236where
237    T: Arithmetic,
238{
239    type Output = Uncertain<T>;
240
241    fn div(self, rhs: T) -> Self::Output {
242        self / Uncertain::point(rhs)
243    }
244}
245
246impl Div<Uncertain<f64>> for f64 {
247    type Output = Uncertain<f64>;
248
249    fn div(self, rhs: Uncertain<f64>) -> Self::Output {
250        Uncertain::point(self) / rhs
251    }
252}
253
254// Negation
255impl<T> Neg for Uncertain<T>
256where
257    T: Neg<Output = T> + Shareable,
258{
259    type Output = Uncertain<T>;
260
261    fn neg(self) -> Self::Output {
262        self.map(|x| -x)
263    }
264}
265
266// Additional mathematical operations for floating point types
267impl Uncertain<f64> {
268    /// Raises the uncertain value to a power
269    ///
270    /// # Example
271    /// ```rust
272    /// use uncertain_rs::Uncertain;
273    ///
274    /// let base = Uncertain::normal(2.0, 0.1);
275    /// let squared = base.pow(2.0);
276    /// ```
277    #[must_use]
278    pub fn pow(&self, exponent: f64) -> Uncertain<f64> {
279        self.map(move |x| x.powf(exponent))
280    }
281
282    /// Takes the square root of the uncertain value
283    ///
284    /// # Example
285    /// ```rust
286    /// use uncertain_rs::Uncertain;
287    ///
288    /// let positive = Uncertain::uniform(1.0, 100.0);
289    /// let sqrt_val = positive.sqrt();
290    /// ```
291    #[must_use]
292    pub fn sqrt(&self) -> Uncertain<f64> {
293        self.map(f64::sqrt)
294    }
295
296    /// Takes the natural logarithm of the uncertain value
297    ///
298    /// # Example
299    /// ```rust
300    /// use uncertain_rs::Uncertain;
301    ///
302    /// let positive = Uncertain::uniform(0.1, 10.0);
303    /// let ln_val = positive.ln();
304    /// ```
305    #[must_use]
306    pub fn ln(&self) -> Uncertain<f64> {
307        self.map(f64::ln)
308    }
309
310    /// Takes the exponential of the uncertain value
311    ///
312    /// # Example
313    /// ```rust
314    /// use uncertain_rs::Uncertain;
315    ///
316    /// let normal = Uncertain::normal(0.0, 1.0);
317    /// let exp_val = normal.exp();
318    /// ```
319    #[must_use]
320    pub fn exp(&self) -> Uncertain<f64> {
321        self.map(f64::exp)
322    }
323
324    /// Takes the absolute value of the uncertain value
325    ///
326    /// # Example
327    /// ```rust
328    /// use uncertain_rs::Uncertain;
329    ///
330    /// let normal = Uncertain::normal(0.0, 2.0);
331    /// let abs_val = normal.abs();
332    /// ```
333    #[must_use]
334    pub fn abs(&self) -> Uncertain<f64> {
335        self.map(f64::abs)
336    }
337
338    /// Applies sine function to the uncertain value
339    #[must_use]
340    pub fn sin(&self) -> Uncertain<f64> {
341        self.map(f64::sin)
342    }
343
344    /// Applies cosine function to the uncertain value
345    #[must_use]
346    pub fn cos(&self) -> Uncertain<f64> {
347        self.map(f64::cos)
348    }
349
350    /// Applies tangent function to the uncertain value
351    #[must_use]
352    pub fn tan(&self) -> Uncertain<f64> {
353        self.map(f64::tan)
354    }
355}
356
357#[cfg(test)]
358mod tests {
359    use super::*;
360
361    #[test]
362    fn test_addition() {
363        let x = Uncertain::point(5.0);
364        let y = Uncertain::point(3.0);
365        let sum = x + y;
366        assert!((sum.sample() - 8.0_f64).abs() < f64::EPSILON);
367    }
368
369    #[test]
370    fn test_scalar_addition() {
371        let x = Uncertain::point(5.0);
372        let sum = x + 3.0;
373        assert!((sum.sample() - 8.0_f64).abs() < f64::EPSILON);
374
375        let sum2 = 3.0 + Uncertain::point(5.0);
376        assert!((sum2.sample() - 8.0_f64).abs() < f64::EPSILON);
377    }
378
379    #[test]
380    fn test_multiplication() {
381        let x = Uncertain::point(4.0);
382        let y = Uncertain::point(3.0);
383        let product = x * y;
384        assert!((product.sample() - 12.0_f64).abs() < f64::EPSILON);
385    }
386
387    #[test]
388    fn test_complex_expression() {
389        let x = Uncertain::point(2.0);
390        let y = Uncertain::point(3.0);
391        let result = (x + y) * 2.0 - 1.0;
392        assert!((result.sample() - 9.0_f64).abs() < f64::EPSILON); // (2 + 3) * 2 - 1 = 9
393    }
394
395    #[test]
396    fn test_mathematical_functions() {
397        let x = Uncertain::point(4.0);
398        assert!((x.sqrt().sample() - 2.0_f64).abs() < f64::EPSILON);
399
400        let y = Uncertain::point(2.0);
401        assert!((y.pow(3.0).sample() - 8.0_f64).abs() < f64::EPSILON);
402    }
403}