-
Notifications
You must be signed in to change notification settings - Fork 342
Add view_re_im and view_mut_re_im methods #1029
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add view_re_im and view_mut_re_im methods #1029
Conversation
These methods make it possible to obtain views of the real and imaginary components of elements in an array of complex elements.
src/numeric/mod.rs
Outdated
|
||
impl<T, S, D> ArrayBase<S, D> | ||
where | ||
S: Data<Elem = Complex<T>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fantastic. I wonder if there should be a constraint on T. We have ensured that Complex is repr(C). Any other constraints on T needed?
The fun return type of the method inspires us to think that what happens if the user inputs an Array<Complex<ArrayView<_, _>>>
here? ArrayView are Copy but would it truly be a defined operation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fantastic. I wonder if there should be a constraint on T. We have ensured that Complex is repr(C). Any other constraints on T needed?
I don't think so, but the guidelines aren't very clear on this. The relevant references here are the nomicon and the unsafe code guidelines. The re
field is guaranteed to be at offset 0
in the struct, and the im
field is guaranteed to be after it, as described by the C17 standard (which is referenced by the unsafe code guidelines):
Within a structure object, the non-bit-field members and the units in which bit-fields reside have addresses that increase in the order in which they are declared. A pointer to a structure object,suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides), and vice versa. There may be unnamed padding within a structure object, but not at its beginning.
The possibility of padding in Complex<T>
is somewhat unclear. Maybe, to be on the safe side, we should add a few asserts (which should be eliminated by the compiler at compile time):
assert_eq!(mem::size_of::<Complex<T>>(), mem::size_of::<T>().checked_mul(2).unwrap());
assert_eq!(mem::align_of::<Complex<T>>(), mem::align_of::<T>());
// Non-empty case only:
let ptr = self.ptr.as_ptr();
assert_eq!(
unsafe { &(*ptr).im as *const T as usize }
.checked_sub(ptr as usize)
.unwrap(),
mem::size_of::<T>(),
);
The fun return type of the method inspires us to think that what happens if the user inputs an
Array<Complex<ArrayView<_, _>>>
here? ArrayView are Copy but would it truly be a defined operation?
Yeah, that would be a weird type. Arrays containing arrays are pretty weird. I don't see any soundness issues with it, though. I think the implementation should work for arbitrary T
. The return type of Array::<Complex<ArrayView<'a, _, _>, D>::view_re_im
would be Complex<ArrayView<'b, ArrayView<'a, _, _>, D>>
. In other words, you'd get a Complex
struct where the re
and im
fields would each be a view containing views.
Why shouldn't the interface just be something like the methods This is an interesting feature, the hard part is just how to make it available to all custom structs. I think that the rustic solution would be a derive proc macro that creates and implements a custom extension trait: #[derive(NdarrayStructFields)]
struct Foo {
a: i32,
b: f32,
}
/* adds and implements the following trait for arrays */
trait NdarrayStructFields_Foo {
fn view_a(&self) -> ArrayView<i32, ..>;
fn view_b(&self) -> ArrayView<f32, ..>;
/* view_mut_a, ... */
} It would depend on a stable offsetof operation, I guess. A macro could be added as a 90% solution instead of a proc macro. I don't foresee that it's something we implement ourselves if we don't want to (can be external). |
That would be fine for the immutable case but would be less versatile for the mutable case, since the user wouldn't be able to get mutable views of the real and imaginary parts at the same time. Maybe it would be better to call these methods
Right, that would be my thinking too. The An alternative design would be a more dynamic approach like this: pub unsafe trait FieldAccess<X> {
// Returns the offset in the struct of the field with the specified name.
fn field_offset(&self, name: &str) -> Result<usize, FieldAccessError>;
}
pub enum FieldAccessError {
// There are no fields with the specified name in the struct.
MissingField,
// The specified type of the field is incorrect.
WrongFieldType,
// The size of the struct is not evenly divisible by the size of the field.
//
// This is a requirement because strides in `ndarray` are in units of the element type,
// so the stride in units of bytes must be evenly divisible by the size of the element.
StructSizeNotMultipleOfFieldSize,
}
impl<A, S, D> ArrayBase<S, D>
where
S: Data<Elem = A>,
D: Dimension,
{
pub fn view_field<X>(&self, name: &str) -> Result<ArrayView<'_, X, D>, FieldAccessError>
where
A: FieldAccess<X>,
{ ... }
} (The user's struct would implement An external crate could provide all this functionality if we expose a way to construct a view with arbitrary strides (including negative strides). For now, though, providing this functionality for |
Yes, using the split idea seems good, should have mostly upsides. Only hard part regardless is discoverability. |
I like the approach in this PR, so I'm fine with including this when it's ready.
|
I've pushed a commit which switches from Unfortunately, impl<'a, T, D> ArrayView<'a, Complex<T>, D>
where
D: Dimension,
{
pub fn split_re_im(self) -> Complex<ArrayView<'a, T, D>> {
...
}
} as impl<'a, T, D> ArrayBase<ViewRepr<&'a A>, D>
where
D: Dimension,
{
pub fn split_re_im(self) -> Complex<ArrayView<'a, T, D>> {
...
}
} I spent a little time trying to write a minimal example to demonstrate this issue in order to report it to pub struct ArrayBase<S, D: Dimension>(S, D);
pub struct ViewRepr<T>(T);
pub trait Dimension {}
type ArrayView<'a, A, D> = ArrayBase<ViewRepr<&'a A>, D>;
mod foo {
use crate::{ArrayView, Dimension};
use num_complex::Complex;
impl<'a, A, D> ArrayView<'a, A, D>
where
D: Dimension,
{
pub fn bar(self) {}
}
impl<'a, T, D> ArrayView<'a, Complex<T>, D>
where
D: Dimension,
{
pub fn foo(self) -> Complex<ArrayView<'a, T, D>> {
unimplemented!()
}
}
} Anyway, I don't think the |
These methods make it possible to obtain views of the real and imaginary components of elements in an array of complex elements. For example:
I've created this PR as a draft because I'm not sure where to put these methods in the code and because I need to add more tests. Also, what do you think of the names of the methods? Perhaps a better design would be
split_re_im
methods forArrayView
andArrayViewMut
.Fwiw, NumPy has more general implementation of this type of functionality with arbitrary structs as elements instead of just complex numbers. In principle, we could do the same thing. We'd need a trait to constrain the allowable element types, and if we wanted to support all the structs NumPy can, we'd need to change our strides to be in units of bytes rather than in units of elements. Implementing that would be a lot of work, though, and this simple support for complex numbers is useful today.