22
33#[ cfg( feature = "std" ) ]
44use num_traits:: Float ;
5+ #[ cfg( feature = "std" ) ]
6+ use num_traits:: FloatConst ;
7+ #[ cfg( feature = "std" ) ]
8+ use num_complex:: Complex ;
59
610use crate :: imp_prelude:: * ;
711
12+ /// Trait for values with a meaningful complex argument (phase).
13+ /// This `*_64` version standardises the *output* to `f64`.
14+ #[ cfg( feature = "std" ) ]
15+ pub trait HasAngle64 {
16+ /// Return the phase angle (argument) in radians in the range (-π, π].
17+ fn to_angle64 ( & self ) -> f64 ;
18+ }
19+
20+ #[ cfg( feature = "std" ) ]
21+ impl HasAngle64 for f64 {
22+ #[ inline]
23+ fn to_angle64 ( & self ) -> f64 {
24+ ( 0.0f64 ) . atan2 ( * self )
25+ }
26+ }
27+
28+ #[ cfg( feature = "std" ) ]
29+ impl HasAngle64 for f32 {
30+ #[ inline]
31+ fn to_angle64 ( & self ) -> f64 {
32+ // Promote to f64
33+ ( 0.0f64 ) . atan2 ( * self as f64 )
34+ }
35+ }
36+
37+ #[ cfg( feature = "std" ) ]
38+ impl HasAngle64 for Complex < f64 > {
39+ #[ inline]
40+ fn to_angle64 ( & self ) -> f64 {
41+ self . im . atan2 ( self . re )
42+ }
43+ }
44+
45+ #[ cfg( feature = "std" ) ]
46+ impl HasAngle64 for Complex < f32 > {
47+ #[ inline]
48+ fn to_angle64 ( & self ) -> f64 {
49+ ( self . im as f64 ) . atan2 ( self . re as f64 )
50+ }
51+ }
52+
53+ /// Optional: precision-preserving variant (returns `F`), if you want
54+ /// an API that keeps `f32` outputs for `f32` inputs.
55+ ///
56+ /// - Works for `f32`/`f64` and `Complex<f32>`/`Complex<f64>`.
57+ #[ cfg( feature = "std" ) ]
58+ pub trait HasAngle < F : Float + FloatConst > {
59+ /// Return the phase angle (argument) in the same precision as the input type.
60+ fn to_angle ( & self ) -> F ;
61+ }
62+
63+ #[ cfg( feature = "std" ) ]
64+ impl < F > HasAngle < F > for F
65+ where
66+ F : Float + FloatConst ,
67+ {
68+ #[ inline]
69+ fn to_angle ( & self ) -> F {
70+ F :: zero ( ) . atan2 ( * self )
71+ }
72+ }
73+
74+ #[ cfg( feature = "std" ) ]
75+ impl < F > HasAngle < F > for Complex < F >
76+ where
77+ F : Float + FloatConst ,
78+ {
79+ #[ inline]
80+ fn to_angle ( & self ) -> F {
81+ self . im . atan2 ( self . re )
82+ }
83+ }
84+
885#[ cfg( feature = "std" ) ]
986macro_rules! boolean_ops {
1087 ( $( #[ $meta1: meta] ) * fn $func: ident
@@ -167,6 +244,112 @@ where
167244 }
168245}
169246
247+ /// # Angle calculation methods for arrays
248+ ///
249+ /// Methods for calculating phase angles of complex values in arrays.
250+ #[ cfg( feature = "std" ) ]
251+ #[ cfg_attr( docsrs, doc( cfg( feature = "std" ) ) ) ]
252+ impl < A , D > ArrayRef < A , D >
253+ where
254+ A : HasAngle64 ,
255+ D : Dimension ,
256+ {
257+ /// Return the [phase angle (argument)](https://en.wikipedia.org/wiki/Argument_(complex_analysis)) of complex values in the array.
258+ ///
259+ /// This function always returns `f64` values, regardless of input precision.
260+ /// The angles are returned in the range (-π, π].
261+ ///
262+ /// # Arguments
263+ ///
264+ /// * `deg` - If `true`, convert radians to degrees; if `false`, return radians.
265+ ///
266+ /// # Examples
267+ ///
268+ /// ```
269+ /// use ndarray::array;
270+ /// use num_complex::Complex;
271+ /// use std::f64::consts::PI;
272+ ///
273+ /// // Real numbers
274+ /// let real_arr = array![1.0, -1.0, 0.0];
275+ /// let angles_rad = real_arr.angle(false);
276+ /// let angles_deg = real_arr.angle(true);
277+ /// assert!((angles_rad[0] - 0.0).abs() < 1e-10);
278+ /// assert!((angles_rad[1] - PI).abs() < 1e-10);
279+ /// assert!((angles_deg[1] - 180.0).abs() < 1e-10);
280+ ///
281+ /// // Complex numbers
282+ /// let complex_arr = array![
283+ /// Complex::new(1.0, 0.0),
284+ /// Complex::new(0.0, 1.0),
285+ /// Complex::new(1.0, 1.0),
286+ /// ];
287+ /// let angles = complex_arr.angle(false);
288+ /// assert!((angles[0] - 0.0).abs() < 1e-10);
289+ /// assert!((angles[1] - PI/2.0).abs() < 1e-10);
290+ /// assert!((angles[2] - PI/4.0).abs() < 1e-10);
291+ /// ```
292+ #[ must_use = "method returns a new array and does not mutate the original value" ]
293+ pub fn angle ( & self , deg : bool ) -> Array < f64 , D >
294+ {
295+ let mut result = self . map ( |x| x. to_angle64 ( ) ) ;
296+ if deg {
297+ result. mapv_inplace ( |a| a * 180.0f64 / std:: f64:: consts:: PI ) ;
298+ }
299+ result
300+ }
301+ }
302+
303+ /// # Precision-preserving angle calculation methods
304+ ///
305+ /// Methods for calculating phase angles that preserve input precision.
306+ #[ cfg( feature = "std" ) ]
307+ #[ cfg_attr( docsrs, doc( cfg( feature = "std" ) ) ) ]
308+ impl < A , D > ArrayRef < A , D >
309+ where
310+ D : Dimension ,
311+ {
312+ /// Return the [phase angle (argument)](https://en.wikipedia.org/wiki/Argument_(complex_analysis)) of values, preserving input precision.
313+ ///
314+ /// This method preserves the precision of the input:
315+ /// - `f32` and `Complex<f32>` inputs produce `f32` outputs
316+ /// - `f64` and `Complex<f64>` inputs produce `f64` outputs
317+ ///
318+ /// # Arguments
319+ ///
320+ /// * `deg` - If `true`, convert radians to degrees; if `false`, return radians.
321+ ///
322+ /// # Examples
323+ ///
324+ /// ```
325+ /// use ndarray::array;
326+ /// use num_complex::Complex;
327+ ///
328+ /// // f32 precision preserved for complex numbers
329+ /// let complex_f32 = array![Complex::new(1.0f32, 1.0f32)];
330+ /// let angles_f32 = complex_f32.angle_preserve(false);
331+ /// // angles_f32 has type Array<f32, _>
332+ ///
333+ /// // f64 precision preserved for complex numbers
334+ /// let complex_f64 = array![Complex::new(1.0f64, 1.0f64)];
335+ /// let angles_f64 = complex_f64.angle_preserve(false);
336+ /// // angles_f64 has type Array<f64, _>
337+ /// ```
338+ #[ must_use = "method returns a new array and does not mutate the original value" ]
339+ pub fn angle_preserve < F > ( & self , deg : bool ) -> Array < F , D >
340+ where
341+ A : HasAngle < F > ,
342+ F : Float + FloatConst ,
343+ {
344+ let mut result = self . map ( |x| x. to_angle ( ) ) ;
345+ if deg {
346+ let factor = F :: from ( 180.0 ) . unwrap ( ) / F :: PI ( ) ;
347+ result. mapv_inplace ( |a| a * factor) ;
348+ }
349+ result
350+ }
351+ }
352+
170353impl < A , D > ArrayRef < A , D >
171354where
172355 A : ' static + PartialOrd + Clone ,
@@ -191,3 +374,150 @@ where
191374 self . mapv ( |a| num_traits:: clamp ( a, min. clone ( ) , max. clone ( ) ) )
192375 }
193376}
377+
378+ /// Calculate the [phase angle (argument)](https://en.wikipedia.org/wiki/Argument_(complex_analysis)) of complex values in an array.
379+ ///
380+ ///
381+ /// Always returns `f64`, regardless of input precision.
382+ ///
383+ /// # Arguments
384+ ///
385+ /// * `z` - Array of real or complex values (f32/f64, `Complex<f32>`/`Complex<f64>`).
386+ /// * `deg` - If `true`, convert radians to degrees.
387+ ///
388+ /// # Returns
389+ ///
390+ /// An `Array<f64, D>` with the same shape as `z` containing the phase angles.
391+ /// Angles are in the range (-π, π] for radians, or (-180, 180] for degrees.
392+ ///
393+ /// # Examples
394+ ///
395+ /// ```
396+ /// use ndarray::array;
397+ /// use num_complex::Complex;
398+ /// use std::f64::consts::PI;
399+ ///
400+ /// // Real numbers
401+ /// let real_vals = array![1.0, -1.0, 0.0];
402+ /// let angles_rad = ndarray::angle(&real_vals, false);
403+ /// let angles_deg = ndarray::angle(&real_vals, true);
404+ /// assert!((angles_rad[0] - 0.0).abs() < 1e-10);
405+ /// assert!((angles_rad[1] - PI).abs() < 1e-10);
406+ /// assert!((angles_deg[1] - 180.0).abs() < 1e-10);
407+ ///
408+ /// // Complex numbers
409+ /// let complex_vals = array![
410+ /// Complex::new(1.0, 0.0),
411+ /// Complex::new(0.0, 1.0),
412+ /// Complex::new(1.0, 1.0),
413+ /// ];
414+ /// let angles = ndarray::angle(&complex_vals, false);
415+ /// assert!((angles[0] - 0.0).abs() < 1e-10);
416+ /// assert!((angles[1] - PI/2.0).abs() < 1e-10);
417+ /// assert!((angles[2] - PI/4.0).abs() < 1e-10);
418+ /// ```
419+ ///
420+ /// # Zero handling
421+ ///
422+ /// The function follows NumPy's convention for handling zeros:
423+ /// - `+0 + 0i` → `+0`
424+ /// - `-0 + 0i` → `+π`
425+ /// - `+0 - 0i` → `-0`
426+ /// - `-0 - 0i` → `-π`
427+ #[ cfg( feature = "std" ) ]
428+ #[ cfg_attr( docsrs, doc( cfg( feature = "std" ) ) ) ]
429+ pub fn angle < T , S , D > ( z : & ArrayBase < S , D > , deg : bool ) -> Array < f64 , D >
430+ where
431+ T : HasAngle64 ,
432+ S : Data < Elem = T > ,
433+ D : Dimension ,
434+ {
435+ let mut result = z. map ( |x| x. to_angle64 ( ) ) ;
436+ if deg {
437+ result. mapv_inplace ( |a| a * 180.0f64 / std:: f64:: consts:: PI ) ;
438+ }
439+ result
440+ }
441+
442+ /// Scalar convenience function for angle calculation.
443+ ///
444+ /// Calculate the [phase angle (argument)](https://en.wikipedia.org/wiki/Argument_(complex_analysis)) of a single complex value.
445+ ///
446+ /// # Arguments
447+ ///
448+ /// * `z` - A real or complex value (f32/f64, `Complex<f32>`/`Complex<f64>`).
449+ /// * `deg` - If `true`, convert radians to degrees.
450+ ///
451+ /// # Returns
452+ ///
453+ /// The phase angle as `f64` in radians or degrees.
454+ ///
455+ /// # Examples
456+ ///
457+ /// ```
458+ /// use num_complex::Complex;
459+ /// use std::f64::consts::PI;
460+ ///
461+ /// assert!((ndarray::angle_scalar(Complex::new(1.0, 1.0), false) - PI/4.0).abs() < 1e-10);
462+ /// assert!((ndarray::angle_scalar(1.0f32, true) - 0.0).abs() < 1e-10);
463+ /// assert!((ndarray::angle_scalar(-1.0, true) - 180.0).abs() < 1e-10);
464+ /// ```
465+ #[ cfg( feature = "std" ) ]
466+ #[ cfg_attr( docsrs, doc( cfg( feature = "std" ) ) ) ]
467+ pub fn angle_scalar < T : HasAngle64 > ( z : T , deg : bool ) -> f64
468+ {
469+ let mut a = z. to_angle64 ( ) ;
470+ if deg {
471+ a *= 180.0f64 / std:: f64:: consts:: PI ;
472+ }
473+ a
474+ }
475+
476+ /// Precision-preserving angle calculation function.
477+ ///
478+ /// Calculate the phase angle of complex values while preserving input precision.
479+ /// Unlike [`angle`], this function returns the same precision as the input:
480+ /// - `f32` and `Complex<f32>` inputs produce `f32` outputs
481+ /// - `f64` and `Complex<f64>` inputs produce `f64` outputs
482+ ///
483+ /// # Arguments
484+ ///
485+ /// * `z` - Array of real or complex values.
486+ /// * `deg` - If `true`, convert radians to degrees.
487+ ///
488+ /// # Returns
489+ ///
490+ /// An `Array<F, D>` with the same shape as `z` and precision matching the input.
491+ ///
492+ /// # Examples
493+ ///
494+ /// ```
495+ /// use ndarray::array;
496+ /// use num_complex::Complex;
497+ ///
498+ /// // f32 precision preserved for complex numbers
499+ /// let z32 = array![Complex::new(0.0f32, 1.0)];
500+ /// let out32 = ndarray::angle_preserve(&z32, false);
501+ /// // out32 has type Array<f32, _>
502+ ///
503+ /// // f64 precision preserved for complex numbers
504+ /// let z64 = array![Complex::new(0.0f64, -1.0)];
505+ /// let out64 = ndarray::angle_preserve(&z64, false);
506+ /// // out64 has type Array<f64, _>
507+ /// ```
508+ #[ cfg( feature = "std" ) ]
509+ #[ cfg_attr( docsrs, doc( cfg( feature = "std" ) ) ) ]
510+ pub fn angle_preserve < A , F , S , D > ( z : & ArrayBase < S , D > , deg : bool ) -> Array < F , D >
511+ where
512+ A : HasAngle < F > ,
513+ F : Float + FloatConst ,
514+ S : Data < Elem = A > ,
515+ D : Dimension ,
516+ {
517+ let mut result = z. map ( |x| x. to_angle ( ) ) ;
518+ if deg {
519+ let factor = F :: from ( 180.0 ) . unwrap ( ) / F :: PI ( ) ;
520+ result. mapv_inplace ( |a| a * factor) ;
521+ }
522+ result
523+ }
0 commit comments