Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Generalize arithmetic ops to more combinations of scalars and arrays#782

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
jturner314 wants to merge3 commits intorust-ndarray:master
base:master
Choose a base branch
Loading
fromjturner314:complex-real-ops

Conversation

jturner314
Copy link
Member

@jturner314jturner314 commentedFeb 15, 2020
edited
Loading

This PR generalizes the existing implementations of arithmetic operations between scalars and arrays to more combinations of types. This is especially useful for operations between complex and real types.

A couple of notes:

  • This removes the special handling of commutative operations. (Before, commutative operations with the scalar on the left side were implemented by calling the operation with the scalar on the right side.) IMO, implementations for more combinations of types are more important than possible differences in compile time due to reusing implementations.

  • The new&arr (op) scalar implementation brings a performance boost.

  • An alternative approach for the "scalar on lhs" operations would be to add more implementations for specific combinations of types, e.g.f32 andComplex<f32>. I chose the generic approach instead for its conciseness and flexibility.

  • This change is backwards compatible, except for possible changes in type inference due to the implementations for more combinations of types.

Fixes#781.

bluss reacted with thumbs up emoji
@jturner314
Copy link
MemberAuthor

It appears that Rust 1.37 has a bug that prevents this PR from working properly. Fortunately, this bug isn't present in the latest stable compiler, but we'll need to wait until we bump the minimum required Rust version before merging this PR.

@blussbluss added this to the0.14.0 milestoneApr 22, 2020
@bluss
Copy link
Member

Delightful that the.to_owned() was so easy to remove just like that

@blussbluss self-requested a reviewDecember 9, 2020 15:53
@blussbluss removed the postponed labelDec 9, 2020
This change has two benefits:* The new implementation applies to more combinations of types. For  example, it now applies to `&Array2<f32>` and `Complex<f32>`.* The new implementation avoids cloning the elements twice, and it  avoids iterating over the elements twice. (The old implementation  called `.to_owned()` followed by the arithmetic operation, while the  new implementation clones the elements and performs the arithmetic  operation in the same iteration.)On my machine, this change improves the performance for bothcontiguous and discontiguous arrays. (`scalar_add_1/2` go from ~530ns/iter to ~380 ns/iter, and `scalar_add_strided_1/2` go from ~1540ns/iter to ~1420 ns/iter.)
This doesn't have a noticeable impact on the results of the`scalar_add_2` and `scalar_add_strided_2` benchmarks.
@bluss
Copy link
Member

Rebased to current master

$scalar: Clone + $trt<A, Output=B>,
A: Clone,
S: Data<Elem=A>,
D: Dimension,
Copy link
Member

@blussblussDec 29, 2020
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This impl somehow now breaks Rust -- see the failed tests -- and causes a recursion errror - for an expression that has typef32 +f32 which is quite strange/scary(!)

   --> tests/oper.rs:159:48    |159 |         .fold(f32::zero(), |acc, (&x, &y)| acc + x * y)    |                                                ^    |    = help: consider adding a `#![recursion_limit="256"]` attribute to your crate (`oper`)    = note: required because of the requirements on the impl of `Add<&ndarray::ArrayBase<_, _>>` for `f32`    = note: required because of the requirements on the impl of `Add<&ndarray::ArrayBase<_, _>>` for `f32`    = note: required because of the requirements on the impl of `Add<&ndarray::ArrayBase<_, _>>` for `f32`    = note: required because of the requirements on the impl of `Add<&ndarray::ArrayBase<_, _>>` for `f32`    = note: required because of the requirements on the impl of `Add<&ndarray::ArrayBase<_, _>>` for `f32`    = note: required because of the requirements on the impl of `Add<&ndarray::ArrayBase<_, _>>` for `f32`    = note: required because of the requirements on the impl of `Add<&ndarray::ArrayBase<_, _>>` for `f32`    = note: required because of the requirements on the impl of `Add<&ndarray::ArrayBase<_, _>>` for `f32`

Unsure if this is a Rust bug - for example that the impl is accepted(?), but I think this impl is too general and has infinite descent.

Given the question iff32 implementsAdd<&ArrayBase<S, D>> look for other impl that hasf32: Add<A> whereS: Data<Elem=A> which looks recursive, is that it?

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

It looks like a compiler bug to me. As you point out, the expression involves onlyf32, but for some reason, the error message indicates that one of the arguments is an array. It's also interesting that on my machine with Rust 1.48.0, the error message is slightly different, saying "impl ofAdd<ndarray::ArrayBase<_, _>> forf32" instead of the error message in your comment "impl ofAdd<&ndarray::ArrayBase<_, _>> forf32". (Note the&.)

The function fails to compile (with the same error message) even after adding type annotations:

fnreference_dot<'a,V1,V2>(a:V1,b:V2) ->f32whereV1:AsArray<'a,f32>,V2:AsArray<'a,f32>,{let a:ArrayView1<'a,f32> = a.into();let b:ArrayView1<'a,f32> = b.into();    a.iter().zip(b.iter()).fold(f32::zero(), |acc:f32,(&x,&y):(&f32,&f32)| acc + x* y)}

but if I remove the+ x * y, it compiles successfully:

fnreference_dot<'a,V1,V2>(a:V1,b:V2) ->f32whereV1:AsArray<'a,f32>,V2:AsArray<'a,f32>,{let a:ArrayView1<'a,f32> = a.into();let b:ArrayView1<'a,f32> = b.into();    a.iter().zip(b.iter()).fold(f32::zero(), |acc:f32,(&x,&y):(&f32,&f32)| acc)}

I don't see any reason other than a compiler bug for the first function to fail to compile when the second one compiles without errors, since the type annotations confirm that the closure is operating only onf32 values.

This also compiles successfully:

fnreference_dot2<'a>(a:ArrayView1<'a,f32>,b:ArrayView1<'a,f32>) ->f32{    a.iter().zip(b.iter()).fold(f32::zero(), |acc:f32,(&x,&y):(&f32,&f32)| acc + x* y)}

so the bug involves the.into() calls in some way. It's surprising that adding explicit type annotations for the results of the.into() calls, as in the first example, doesn't work around the bug.

Fwiw, I don't thinkimpl<'a, A, S, D, B> $trt<&'a ArrayBase<S, D>> for $scalar is infinitely recursive, since AFAIK it's not possible to have an array of (arrays of (arrays of (arrays of ... [infinite depth]))). The innermost array type can only have an element type that's not an array. You're right that there is recursion if you're dealing with arrays of arrays, but that's the correct behavior, and the recursion is not infinite.

For the particular function we're looking at, the impl doesn't apply, and I don't think the compiler should be trying to apply it. (I think it should only apply the impl if it knows the RHS has some type&ArrayBase<?S, ?D>, where?S and?D are inference variables.)

bluss reacted with thumbs up emoji
Copy link
Member

@blussblussDec 30, 2020
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Interesting, the test runners for cross_test, stable, mips vs i686 disagree with each other about the error too, in the same way, even if they both use Rust 1.48

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I reported the issue (with a simplified example) atrust-lang/rust#80542.

bluss reacted with thumbs up emoji
@bluss
Copy link
Member

bluss commentedDec 30, 2020
edited
Loading

I have considered the question of deprecating scalars as left hand side (LHS) operands. The reason would be because their implementation does not fit well with how trait impls are normally written, and the inevitable asymmetry between array + scalar and scalar + array in terms of which types are accepted.

@jturner314
Copy link
MemberAuthor

jturner314 commentedDec 31, 2020
edited
Loading

I have considered the question of deprecating scalars as left hand side (LHS) operands.

I agree that the implementations we have are somewhat unsatisfying, but IMO they're useful enough to keep. I would guess that the vast majority of users are dealing with the element types we implement the operators for, probably mostlyf32/f64, and the impls are useful because subtraction and division aren't commutative. (To perform subtraction/division with a scalar on the left side without these impls, you'd have to usemapv orazip, which are much more verbose.)

I suppose an alternative option to the existing impls would be aScalar wrapper type so that you could write expressions like this:

Scalar(2.) / array

which would work with any element type but would be less intuitive and would make expressions more verbose. I'm not sure aScalar wrapper type is much better than usingmapv.

bluss reacted with thumbs up emojibluss reacted with eyes emoji

@bluss
Copy link
Member

bluss commentedJan 10, 2021
edited
Loading

I think I have found out that ifthis (ugly) workaround is applied, the ScalarOperand trait is not needed anymore - meaning an unrestrictedArray1<A> + A would be allowed (withoutA: ScalarOperand). However, I'm unsure if it can be extended toArray1<A> + B - probably not.

I think that Scalar is a lot better thanmapv, just more work for us to introduce it with all the right impls.

jturner314 reacted with thumbs up emoji

@bluss
Copy link
Member

Benchmark and performance improvements are being included by using PR#890, that supersedes just the first commit and themap changes from this PR.

jturner314 reacted with thumbs up emoji

@blussbluss removed this from the0.15.0 milestoneJan 10, 2021
@akern40akern40 linked an issueDec 23, 2024 that may beclosed by this pull request
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@blussblussbluss left review comments

Assignees
No one assigned
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

impl Mul<Array<Complex<T>>> for T Scalar operations with complex array and complex scalars
2 participants
@jturner314@bluss

[8]ページ先頭

©2009-2025 Movatter.jp