Skip to content
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

A_ldiv_B!(a, b) writes over b or not depending on type of a #10787

Closed
KristofferC opened this issue Apr 10, 2015 · 19 comments
Closed

A_ldiv_B!(a, b) writes over b or not depending on type of a #10787

KristofferC opened this issue Apr 10, 2015 · 19 comments
Labels

Comments

@KristofferC
Copy link
Sponsor Member

Is this intended?

julia> a = rand(3,3); b = rand(3)
3-element Array{Float64,1}:
 0.95134 
 0.43887 
 0.719551

julia> A_ldiv_B!(sparse(a), b) 
3-element Array{Float64,1}:
 0.455667
 0.712479
 0.326213

julia> b # Note: b is not overwritten
3-element Array{Float64,1}:
 0.95134 
 0.43887 
 0.719551

julia> A_ldiv_B!(lufact(a), b)
3-element Array{Float64,1}:
 0.455667
 0.712479
 0.326213

julia> b # For this, b is overwritten
3-element Array{Float64,1}:
 0.455667
 0.712479
 0.326213
@KristofferC KristofferC changed the title A_ldiv_B!(a, b) writes over b or not depending on argument type of a A_ldiv_B!(a, b) writes over b or not depending on type of a Apr 10, 2015
@dmbates
Copy link
Member

dmbates commented Apr 10, 2015

It's a misnomer in the Umfpack interface, which is what is used for solving square, non-Hermitian, sparse systems. The A_ldiv_B! method calls the internal solve function in base/linalg/umfpack.jl which does not overwrite the right hand side but instead returns a freshly allocated vector.

Whether or not this is a bug depends on how strongly you interpret the ! in the name A_ldiv_B!, etc. Does it mean that it can overwrite the argument B or does it mean that it must overwrite the argument B?

@dmbates
Copy link
Member

dmbates commented Apr 10, 2015

I should add that, as far as I know, there is no mutating solver in Umfpack, so the only way you could get mutating behavior in Julia is to allocate a vector or matrix for the solution, solve the system, then copy the solution back into the right hand side.

@tkelman
Copy link
Contributor

tkelman commented Apr 10, 2015

I think a no-method error would be preferable in cases where a mutating operation is not available, rather than trying to mimic an in-place operation by allocating and copying.

@toivoh
Copy link
Contributor

toivoh commented Apr 10, 2015 via email

@tkelman
Copy link
Contributor

tkelman commented Apr 10, 2015

Having the in-place version perform slower and allocate [as much or] more memory than the standard case defeats the purpose of having it at all. If you want code to be generic and not every type can implement an updating operation, then you should probably be using the conventional operation. If you want the highest possible performance, your code would need to be aware of the types it's operating on, use in-place for types where it is faster, or the standard form when an in-place operation is not available.

Yes it would be ideal if every single type supported in-place operations and they would always be the higher-performance option, then the copying operation would be always be a trivial generic wrapper around the in-place version. But this is not currently the case.

edit: The current situation of having an in-place operator that isn't actually in-place is semantically confusing, but at least doesn't incur a performance cost due to extra copying. So maybe it's acceptable for now?

@nalimilan
Copy link
Member

@tkelman That's a tough call. You may want your algorithm to be as fast as possible for types that support in-place operations, and still work (even if less efficiently) for other types. The alternatives are 1) the function fails plainly when given a type that doesn't support in-place operation, or 2) the developer checks for the existence of the in-place method for the type, and falls back to the copying more generic function.

But I agree that A_*B! functions imply in their name that the operation is in-place, and that silently making a copy with some types can be confusing. Maybe with an improved syntax for in-place operations (#249), falling back to an allocating version when needed would be more acceptable.

@toivoh
Copy link
Contributor

toivoh commented Apr 11, 2015 via email

@KristofferC
Copy link
Sponsor Member Author

Personally, I expect some homogeneity in the A_op_B methods. It feels dangerous if you try to write a general method for matrices and you get different outputs depending on the representation you chose for the matrix.

I think that there are two possible choices:

  • Don't have a mutating method for the types where there is no in-place solver.
  • Manually do the mutation of, in this case, b.

Keeping the current situation feels like it can lead to very subtle bugs in user code.

@tkelman
Copy link
Contributor

tkelman commented Apr 11, 2015

Manual mutation and incurring the associated performance penalty might be preferable to a no-method error, but then you have a more subtle performance discrepancy that depends on the input types.

@toivoh
Copy link
Contributor

toivoh commented Apr 12, 2015

We do provide generic fallbacks for some AbstractArray methods. I think this could be seen to be in a similar spirit.

Btw had anyone timed this to see how much efficiency is actually lost in this case?

@hayd
Copy link
Member

hayd commented Apr 16, 2015

I always read ! as "may modify some/all arguments" rather than "inplace", specifically in this case I've no reason to think that a remains fixed unless I read the documentation (or source). For code clarity I'd always write:

b = A_ldiv_B!(a, b)  # or perhaps I'd assign it to a new variable

Similarly you don't really lose anything by writing:

s = sort!(s)

but the documentation says clearly you don't need to.

@toivoh
Copy link
Contributor

toivoh commented Apr 16, 2015

In general I agree that the ! doesn't mean that a function has to modify to its arguments, but for in place versions of operations like A_mul_B!, I think it has come to be expected. It seems like an invitation to subtle bugs if we make the mutation optional.

I also seem to remember from a previous discussion that always assigning the return value back to the corresponding variable might make some optimizations harder for the compiler.

@KristofferC
Copy link
Sponsor Member Author

Not sure how much it matters, but in Python it seems that methods that operate inplace usually do not return anything, for example list.sort().

If this is good or not, I am not sure. Many times students write something similar to:

def sort_thingy(thingy)
    return thingy.sort()

and get confused when they get None popping up here and there.

@timholy
Copy link
Sponsor Member

timholy commented Apr 17, 2015

Agreed that A_ldiv_B! needs to be consistent: might mutate is sort of like might copy in reshape, and it seems we all agree that needs to change (#10507).

@KristofferC, I think it's better to return something, that way you can do mean(sqrt!(x)).

@KristofferC
Copy link
Sponsor Member Author

@timholy I agree that is is more comfortable to return the argument since you can chain expressions like you mentioned. @toivoh mentioned that it might come at a performance cost. Anyone know more about that?

@pao
Copy link
Member

pao commented Apr 17, 2015

mentioned that it might come at a performance cost

For the explicit reassignment version only, it sounds like, but...

I'm a bit worried about mean(sqrt!(x)), personally--it's probably slower than the equivalent loop, and mutates x as a side effect but not obviously so because the sqrt!() is buried in the expression. Consenting adults and all that, but I wouldn't encourage that style. (After all, the standard for research on human subjects is informed consent. And programmers are still humans.)

@toivoh
Copy link
Contributor

toivoh commented Apr 17, 2015 via email

@JeffBezanson JeffBezanson added the domain:linear algebra Linear algebra label Apr 25, 2015
mauro3 added a commit to mauro3/ODE.jl that referenced this issue Jun 10, 2015
The problem was that A_ldiv_B! does not update B in case A is a sparse
lufact: JuliaLang/julia#10787

Sparse coloring does not work yet.
@matthieugomez
Copy link
Contributor

matthieugomez commented Oct 8, 2015

+1. It's a source of bug. It also makes it harder to understand which operations really allocate in place.

@KristofferC
Copy link
Sponsor Member Author

I close this now, see #13496

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

10 participants