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

Add DiffResult for MArray #18

Merged
merged 13 commits into from
Mar 19, 2024
Merged

Conversation

charleskawczynski
Copy link
Contributor

@charleskawczynski charleskawczynski commented Oct 31, 2020

This PR adds

DiffResult(value::MArray, derivs::Tuple{Vararg{StaticArray}}) = MutableDiffResult(value, derivs)
DiffResult(value::SizedArray, derivs::Tuple{Vararg{StaticArray}}) = MutableDiffResult(value, derivs)

Fixes NLsolve.jl's 254. MArrays are mutable, so I think this needs to be defined differently than SArrays

Before this PR:

julia> using NLsolve
[ Info: Precompiling NLsolve [2774e3e8-f4cf-5e23-947b-6d7e65073b56]

julia> using StaticArrays

julia> function f!(F, x)
          F[1] = (x[1]+3)*(x[2]^3-7)+18
          F[2] = sin(x[2]*exp(x[1])-1)
       end
f! (generic function with 1 method)

julia> FT = Float32;

julia> a = MArray{Tuple{2},FT}(undef);

julia> a .= (0.1, 1.2);

julia> nlsolve(f!, a; autodiff = :forward)
ERROR: MethodError: no method matching extract_jacobian!(::Type{ForwardDiff.Tag{typeof(f!),Float32}}, ::DiffResults.ImmutableDiffResult{1,MArray{Tuple{2},Float32,1,2},Tuple{MArray{Tuple{2,2},Float32,2,4}}}, ::MArray{Tuple{2},ForwardDiff.Dual{ForwardDiff.Tag{typeof(f!),Float32},Float32,2},1,2}, ::Int64)
Closest candidates are:
  extract_jacobian!(::Type{T}, ::AbstractArray, ::AbstractArray, ::Any) where T at kawc\ForwardDiff.jl\src\jacobian.jl:112
  extract_jacobian!(::Type{T}, ::DiffResults.MutableDiffResult, ::AbstractArray, ::Any) where T at kawc\ForwardDiff.jl\src\jacobian.jl:120
Stacktrace:
 [1] vector_mode_jacobian!(::DiffResults.ImmutableDiffResult{1,MArray{Tuple{2},Float32,1,2},Tuple{MArray{Tuple{2,2},Float32,2,4}}}, ::typeof(f!), ::MArray{Tuple{2},Float32,1,2}, ::MArray{Tuple{2},Float32,1,2}, ::ForwardDiff.JacobianConfig{ForwardDiff.Tag{typeof(f!),Float32},Float32,2,Tuple{MArray{Tuple{2},ForwardDiff.Dual{ForwardDiff.Tag{typeof(f!),Float32},Float32,2},1,2},MArray{Tuple{2},ForwardDiff.Dual{ForwardDiff.Tag{typeof(f!),Float32},Float32,2},1,2}}}) at kawc\ForwardDiff.jl\src\jacobian.jl:170
 [2] jacobian!(::DiffResults.ImmutableDiffResult{1,MArray{Tuple{2},Float32,1,2},Tuple{MArray{Tuple{2,2},Float32,2,4}}}, ::Function, ::MArray{Tuple{2},Float32,1,2}, ::MArray{Tuple{2},Float32,1,2}, ::ForwardDiff.JacobianConfig{ForwardDiff.Tag{typeof(f!),Float32},Float32,2,Tuple{MArray{Tuple{2},ForwardDiff.Dual{ForwardDiff.Tag{typeof(f!),Float32},Float32,2},1,2},MArray{Tuple{2},ForwardDiff.Dual{ForwardDiff.Tag{typeof(f!),Float32},Float32,2},1,2}}}, ::Val{false}) at kawc\ForwardDiff.jl\src\jacobian.jl:78
 [3] (::NLSolversBase.var"#fj_forwarddiff!#24"{typeof(f!),ForwardDiff.JacobianConfig{ForwardDiff.Tag{typeof(f!),Float32},Float32,2,Tuple{MArray{Tuple{2},ForwardDiff.Dual{ForwardDiff.Tag{typeof(f!),Float32},Float32,2},1,2},MArray{Tuple{2},ForwardDiff.Dual{ForwardDiff.Tag{typeof(f!),Float32},Float32,2},1,2}}},MArray{Tuple{2},Float32,1,2}})(::MArray{Tuple{2},Float32,1,2}, ::MArray{Tuple{2,2},Float32,2,4}, ::MArray{Tuple{2},Float32,1,2}) at kawc\NLSolversBase.jl\src\objective_types\oncedifferentiable.jl:160
 [4] value_jacobian!!(::OnceDifferentiable{MArray{Tuple{2},Float32,1,2},MArray{Tuple{2,2},Float32,2,4},MArray{Tuple{2},Float32,1,2}}, ::MArray{Tuple{2},Float32,1,2}, ::MArray{Tuple{2,2},Float32,2,4}, ::MArray{Tuple{2},Float32,1,2}) at kawc\NLSolversBase.jl\src\interface.jl:124
 [5] value_jacobian!! at kawc\NLSolversBase.jl\src\interface.jl:122 [inlined]
 [6] trust_region_(::OnceDifferentiable{MArray{Tuple{2},Float32,1,2},MArray{Tuple{2,2},Float32,2,4},MArray{Tuple{2},Float32,1,2}}, ::MArray{Tuple{2},Float32,1,2}, ::Float32, ::Float32, ::Int64, ::Bool, ::Bool, ::Bool, ::Float32, ::Bool, ::NLsolve.NewtonTrustRegionCache{MArray{Tuple{2},Float32,1,2}}) at kawc\NLsolve.jl\src\solvers\trust_region.jl:119
 [7] trust_region at kawc\NLsolve.jl\src\solvers\trust_region.jl:235 [inlined] (repeats 2 times)
 [8] nlsolve(::OnceDifferentiable{MArray{Tuple{2},Float32,1,2},MArray{Tuple{2,2},Float32,2,4},MArray{Tuple{2},Float32,1,2}}, ::MArray{Tuple{2},Float32,1,2}; method::Symbol, xtol::Float32, ftol::Float32, iterations::Int64, store_trace::Bool, show_trace::Bool, extended_trace::Bool, linesearch::Static, linsolve::NLsolve.var"#27#29", factor::Float32, autoscale::Bool, m::Int64, beta::Int64, aa_start::Int64, droptol::Float32) at kawc\NLsolve.jl\src\nlsolve\nlsolve.jl:26
 [9] nlsolve(::Function, ::MArray{Tuple{2},Float32,1,2}; method::Symbol, autodiff::Symbol, inplace::Bool, kwargs::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}) at kawc\NLsolve.jl\src\nlsolve\nlsolve.jl:52
 [10] top-level scope at REPL[7]:1

After this PR:

julia> using NLsolve
[ Info: Precompiling NLsolve [2774e3e8-f4cf-5e23-947b-6d7e65073b56]

julia> using StaticArrays

julia> function f!(F, x)
          F[1] = (x[1]+3)*(x[2]^3-7)+18
          F[2] = sin(x[2]*exp(x[1])-1)
       end
f! (generic function with 1 method)

julia> FT = Float32;

julia> a = MArray{Tuple{2},FT}(undef);

julia> a .= (0.1, 1.2);

julia> nlsolve(f!, a; autodiff = :forward)
Results of Nonlinear Solver Algorithm
 * Algorithm: Trust-region with dogleg and autoscaling
 * Starting Point: Float32[0.1, 1.2]
 * Zero: Float32[0.1, 1.2]
 * Inf-norm of residuals: 1.656801
 * Iterations: 1000
 * Convergence: false
   * |x - x'| < 0.0e+00: false
   * |f(x)| < 1.0e-08: false
 * Function Calls (f): 2
 * Jacobian Calls (df/dx): 1

@mateuszbaran
Copy link

You can also extend this to SizedArray as well (it's also mutable).

@charleskawczynski
Copy link
Contributor Author

Updated.

@charleskawczynski
Copy link
Contributor Author

bump

@charleskawczynski
Copy link
Contributor Author

bump # 2

@charleskawczynski
Copy link
Contributor Author

bump # 3

@gdalle gdalle linked an issue Mar 15, 2024 that may be closed by this pull request
@gdalle
Copy link
Member

gdalle commented Mar 15, 2024

What would you think about #28 instead?

@charleskawczynski
Copy link
Contributor Author

I’m not sure I have strong opinions. This PR seems a bit simpler. Not sure what to do to get attention to this PR… frankly, I don’t even remember what this was for….

@gdalle
Copy link
Member

gdalle commented Mar 19, 2024

Okay @devmotion told me why #28 was a bad idea, so I think this PR is better, and I have the rights to merge it, I just have a few comments

Copy link
Member

@gdalle gdalle left a comment

Choose a reason for hiding this comment

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

These changes ensure that we don't mess up when value and derivs are of different types. What do you think?

src/DiffResults.jl Outdated Show resolved Hide resolved
src/DiffResults.jl Outdated Show resolved Hide resolved
@gdalle
Copy link
Member

gdalle commented Mar 19, 2024

Just gonna add some tests and we should be good to go

Copy link

codecov bot commented Mar 19, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 86.84%. Comparing base (0cb922d) to head (38cbfb1).

Additional details and impacted files
@@           Coverage Diff           @@
##           master      #18   +/-   ##
=======================================
  Coverage   86.84%   86.84%           
=======================================
  Files           1        1           
  Lines          76       76           
=======================================
  Hits           66       66           
  Misses         10       10           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@@ -36,7 +36,7 @@ Return `r::DiffResult`, with output value storage provided by `value` and output
storage provided by `derivs`.

In reality, `DiffResult` is an abstract supertype of two concrete types, `MutableDiffResult`
and `ImmutableDiffResult`. If all `value`/`derivs` are all `Number`s or `StaticArray`s,
and `ImmutableDiffResult`. If all `value`/`derivs` are all `Number`s or `StaticArrays.SArray`s,
Copy link
Member

Choose a reason for hiding this comment

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

That way, if there is even one mutable array in the mix (like MArray), the whole thing becomes a MutableDiffResult

Copy link
Member

Choose a reason for hiding this comment

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

That's wrong though, isn't it? All value and derivs have to be mutable, otherwise the MutableDiffResult will error when trying to update the immutable component.

Copy link
Member

Choose a reason for hiding this comment

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

you're right, so it's the other way around

Copy link
Member

Choose a reason for hiding this comment

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

If any of the `value`/`derivs` is a `Number` or `StaticArrays.SArray`,
then `r` will be immutable. Otherwise, `r` will be mutable.

that's what we want, right?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, to me it seems that's what we want for StaticArrays. The safest option would be to make the ImmutableDiffResult the default DiffResult type, but given the wide range of mutable arrays (without clear way to catch them all apart from - to some extent - ArrayInterface.ismutable) I think it's much easier implementation-wise to only add special cases for StaticArrays. Even though this is also doomed to fail with wrapper types such as Diagonal etc...

Copy link
Member

Choose a reason for hiding this comment

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

Theoretically, if everyone used the API correctly, making Immutable the default would not be breaking. Unfortunately, as seen in #26, SciML and others are playing fast and loose with realiasing.

So from an ecosystem point of view, any transition Mutable -> Immutable has the potential to be breaking

@gdalle gdalle requested a review from devmotion March 19, 2024 06:46
@gdalle
Copy link
Member

gdalle commented Mar 19, 2024

Asking for a review from someone else because these changes could either be considered breaking or a bug fix.

Copy link

@tpapp tpapp left a comment

Choose a reason for hiding this comment

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

LGTM

@gdalle gdalle merged commit f80602e into JuliaDiff:master Mar 19, 2024
6 checks passed
Copy link
Member

@devmotion devmotion left a comment

Choose a reason for hiding this comment

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

I think it would be good to run downdtream tests with ReverseDiff, ForwardDiff, etc to avoid accidentally breaking them.

@devmotion
Copy link
Member

Oh, the PR was already merged? I think generally we should require approval from people with write permissions (ie JuliaDiff members?).

@gdalle
Copy link
Member

gdalle commented Mar 19, 2024

Sorry, I'll undo it. How do you suggest we run those downstream tests though, add FD and RD as test deps?

gdalle added a commit that referenced this pull request Mar 19, 2024
gdalle added a commit that referenced this pull request Mar 19, 2024
@gdalle gdalle mentioned this pull request Mar 19, 2024
gdalle added a commit that referenced this pull request Mar 19, 2024
@gdalle
Copy link
Member

gdalle commented Mar 19, 2024

See #35 for v2

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

Successfully merging this pull request may close these issues.

MVector type yields ImmutableDiffResult Cannot nlsolve with MArray
5 participants