Add phase angle calculation functions for complex arrays
https://en.wikipedia.org/wiki/Argument_(complex_analysis)
Implement phase angle (argument) calculation for complex numbers in arrays, providing NumPy-compatible functionality. The angle represents the phase of a complex number in the complex plane, calculated as atan2(imaginary, real).
Features:
- Calculate phase angles in range (-π, π] for radians, (-180°, 180°] for degrees
- Support for real numbers (f32, f64) and complex numbers (Complex
, Complex ) - Two API variants:
- NumPy-compatible: always returns f64 regardless of input precision
- Precision-preserving: maintains input precision (f32 → f32, f64 → f64)
- Both array methods and standalone functions available
- Proper handling of signed zeros following NumPy conventions:
- angle(+0 + 0i) = +0, angle(-0 + 0i) = +π
- angle(+0 - 0i) = -0, angle(-0 - 0i) = -π
API additions:
- ArrayRef::angle(deg: bool) -> Array<f64, D>
- ArrayRef::angle_preserve(deg: bool) -> Array<InputType, D>
- ndarray::angle(array, deg) -> Array<f64, D>
- ndarray::angle_preserve(array, deg) -> Array<InputType, D>
- ndarray::angle_scalar(value, deg) -> f64
All functions tested, feature-gated with std and include documentation.
Two API variants:
NumPy-compatible: always returns f64 regardless of input precision Precision-preserving: maintains input precision (f32 → f32, f64 → f64)
I'm not sure about this. I think everything we do in ndarray is "Precision-preserving". It's the programmer's job to use the precision he wants. Is this only for NumPy compatibility?
Is this only for NumPy compatibility?
Yes it is. So the motivation for adding the angle functionality was because I was working on porting some Python and Numpy code to Rust and ndarray and I wanted to make sure I was matching the functionality.
think everything we do in ndarray is "Precision-preserving"
This makes sense and was not something I was aware of (first contribution and all that). I have absolutely no ties/explicit need to always promote to f64 and am happy to remove it from the PR.
However, on this it might be good to explicitly mention details like this, even in just the ndarray for numpy users docs. You know make it clearer that the crate does not aim to emulate numpy functionality, even if it was just in a contributions guideline doc.
Provided I remove this how does it seem? Any other concerns? Happy to answer
My opinion is that we shouldn't be doubling our API with "always returns f64 regardless of input precision" functions. If we do it for phase angle, why not for abs, floor, etc.? Moreover, let's say we have to do it for a specific case, I'd name the "preserving" function normally and add a suffix to the non-preserving function, something like phase_64. But don't do that :)
Any other concerns?
Your MR is quite clean! Thank you for contributing. I usually ask for a test when people are adding functions. Nothing big, but at least one test.
I have created a new commit which should address your concerns. There is no longer the HasAngleF64 trait and no more type promotion to f64. The user controls the float type.
Then I added tests to cover the angle functionality
I agree with the reasoning for keeping everything in radians (option 3). The original reference point for this PR was the NumPy function, which included a deg parameter, but I now see that the Rust ecosystem and ndarray follow the standard library convention of using radians throughout.
The main takeaway from this PR, for me, is not the phase-angle functionality itself but how it has surfaced several (seemingly) unwritten conventions in the codebase.
Most of the feedback (all the feedback is very welcome) has not been about the actual calculation logic, but about implicit stylistic and structural expectations. Even the discussion around the added traits has focused on precision and consistency rather than implementation details. To be clear, I don't think this is bad, it's really good. It's about focusing on the actual design and trying to maintain a cohesive system.
As a first-time contributor, it would have been super helpful to have a clearer written guidance on these conventions since much of this knowledge currently seems to exist only through review comments.
Edit: mostly just organising and formalising my thoughts
And I am also aware that adding such guidelines is not an easy task and relies on a lot of "in-house" knowledge that may not be easily surfaced. But just wanted to give my thoughts on the matter.
I still plan on contributing more to ndarray (if you'll have me) and I look forward to reading your thoughts on this.
Totally heard! Contributor guidelines - and generally better and more beginner-friendly documentation - are on the forefront of my mind. I appreciate both your willingness to take feedback and the way you've surfaced your experiences and challenges as a first time (and hopefully repeating!) contributor.
If you're willing to entertain me, would you mind telling me a bit more about how your background with NumPy shaped your expectations when contributing for the first time? My impression is that this is a very common scenario, and one we might want to explicitly address. It's an excellent source of developers that I'd like to welcome, but there are many differences about how ndarray operates. I've considered writing a blog post about this; would a short primer of that kind be helpful to contributors like yourself?
No problem at all. I really appreciate the engagement. I love the crate, it’s essential, and I’ve recently been using it more heavily as the core data structure in my audio library.
In hindsight, I can see that I fell into the trap of direct translation. I tried to port functionality one-to-one without fully reconsidering how it should be realised idiomatically in Rust. Python often uses the “pass all the args and kwargs” pattern for flexibility, while Rust favours composing small, precise functions. The radians-versus-degrees issue is a good example: in Python, you’d naturally include a deg boolean, whereas in Rust the clearer design is to keep everything in radians and let the user call .to_degrees() if needed.
The main takeaways for me are:
ndarrayis not NumPy and is not trying to be the Rust version of it. It has a lot of similarities at the top level but, due to language differences, they will never be the same. But people will always associate the two together.- Rust developers coming from a NumPy background need to be reminded that standard Python practices do not transfer cleanly. It is easy to add optional parameters to make quick progress, but we should be aiming to make the most of Rust’s features.
- Most of the confusion I ran into could be avoided with a short, visible note in the documentation clarifying these differences.
For example, even something as simple as the following paragraph early in the docs would make a big difference:
“ndarray takes inspiration from NumPy but follows Rust’s conventions. Functions favour explicit, strongly typed arguments over dynamic or optional parameters. Users are expected to handle details such as units, precision, and conversions explicitly rather than relying on internal flags or automatic behaviour. More details on common conventions, precision handling, and porting patterns can be found in the style and contributor documentation.”
If you ever do formalise this, I think a few targeted pages would go a long way: a short note on porting NumPy to idiomatic Rust, a summary of core conventions (ndarray expects the user to handle precision and units), and a contributor guide covering structuring and testing expectations. Even lightweight docs would help first-time contributors avoid unforced errors.
Due to the nature of the crate and the proliferation of the NumPy ecosystem people will always conflate the two. There's no just way around it. More and more developers, researchers and hobbyists are looking at Rust for performance and stability and when they look for the ndimensional arrays they're so used to in NumPy they will land on ndarray. So I think explicitly stating these differences and guides is a must.
Also love the idea of a blog post on this. If you want a co-author or even just a proof reader I'm happy to help. I love all these small non-code details of making software simple to use.
I have pushed the next commit which hopefully addresses the comments. If not just let me know.
Are there any more concerns with this? I would love to access this via the crates.io version of the crate rather than point my Cargo.toml to a forked repo. Thanks