-
Notifications
You must be signed in to change notification settings - Fork 75
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
findmin implementation #455
Conversation
Codecov ReportBase: 83.23% // Head: 83.35% // Increases project coverage by
Additional details and impacted files@@ Coverage Diff @@
## master #455 +/- ##
==========================================
+ Coverage 83.23% 83.35% +0.12%
==========================================
Files 22 22
Lines 2994 3040 +46
==========================================
+ Hits 2492 2534 +42
- Misses 502 506 +4
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. ☔ View full report at Codecov. |
May I propose a more unified API? We could also extend findall(islocalmin, poly)
findall(islocalmax, poly)
findfirst(islocalmin, poly)
findfirst(islocalmax, poly)
findlast(islocalmin, poly)
findlast(islocalmax, poly)
findnext(islocalmin, poly, i)
findnext(islocalmax, poly, i)
findprev(islocalmin, poly, i)
findprev(islocalmax, poly, i) I am no expert in polynomials, but I could help to make a PR for such API extension. |
Hmm, I'll have to mull this over, Certainly |
Please give me a few minutes to try to propose a new API. |
Sure |
Ok, I found that export iscriticalpoint, islocalmin, islocalmax, eachcriticalpoint, eachlocalmin, eachlocalmax
function iscriticalpoint end
function islocalmin end
function islocalmax end
struct EachPoint{F,T<:Polynomial}
f::F
poly::T
end
eachcriticalpoint(poly::Polynomial) = EachPoint(iscriticalpoint, poly)
eachlocalmin(poly::Polynomial) = EachPoint(islocalmin, poly)
eachlocalmax(poly::Polynomial) = EachPoint(islocalmax, poly)
function Base.iterate(iter::EachPoint{typeof(iscriticalpoint)})
# If there are no critical points, return nothing
end
function Base.iterate(iter::EachPoint{typeof(iscriticalpoint)}, state)
# First determine how many critical points there are
if state > length(iter)
return nothing
else
return x, state + 1 # `x` is the `state`th critical point
end
end
function Base.iterate(iter::EachPoint{typeof(islocalmin)})
# If there are no local minima, return nothing
end
function Base.iterate(iter::EachPoint{typeof(islocalmin)}, state)
if state > length(iter)
return nothing
else
return x, state + 1 # `x` is the `state`th local minimum
end
end
function Base.iterate(iter::EachPoint{typeof(islocalmax)})
# If there are no local maxima, return nothing
end
function Base.iterate(iter::EachPoint{typeof(islocalmax)}, state)
if state > length(iter)
return nothing
else
return x, state + 1 # `x` is the `state`th local maximum
end
end
Base.length(iter::EachPoint) = 100 # Calculate how many critical points there are I have not thought about it thoroughly, since I do not know how you calculate the critical points, local minima, and local maxima. I think you calculate all the critical points at once, then check if each one of them is a local minimum or maximum, is it? The This idea is from |
An iterator over local minima might look like this:
The ugly part is the first derivative test along with worrying a bit about floating point issues. A I don't mind implementing these, but I'm a bit wary about exporting the constructor, as it isn't really a specialization of some generic function I know of. |
Forgive me if I do not understand your code very well. r, rst... = critical_pts
pts = [r]
for i ∈ 1:(n-1)
r,s = critical_pts[i], critical_pts[i+1]
!isapprox(r, s; atol=δ, rtol=ϵ) && push!(pts, s)
r = s
end Are you comparing two roots? And if they are too close to distinguish, just discard one of them? And at this part, I am completely lost: lpts = similar(pts, 0)
for i ∈ 1:(length(pts)-1)
r,s = pts[i], pts[i+1]
Δ = s/2 - r/2
y = p′(r + Δ)
rsgn = abs(y) <= δ + (r + Δ) * ϵ ? zero(S) : y < 0 ? -one(S) : one(S)
lsgn * rsgn < 0 && push!(lpts, r)
lsgn = rsgn
end
rsgn = aₙ > 0 ? one(S) : -one(S)
lsgn * rsgn < 0 && push!(lpts, pts[end]) Are you testing each critical point in a small neighborhood with radius I do not think export the constructor is an issue here, as I wrote above, we could write a fake function |
Yeah, the issues with roots are many:
Great, critical points are pi and e, and since pi is double, it is not a local max. But taking roots gives:
Oops, it won't be identified. We have the method
But that misses some other roots it should get when there aren't multiplicities, though perhaps that is best to identify the critical points (instead of checking nearby roots). This is part of the reluctance to add this, as it may not yield correct answers in fairly simple cases. |
Oh, I was thinking we may not want to get all the critical points in one go (because it takes time to do so), so we needed some kind of iterative steps to find them one by one (lazily). But is it true that we find them all at once? If so, there is no need for I like the current implementation very much. If I understand correctly, you use |
However, there is one potential performance issue: if we call |
True, |
True. Maybe we could write something in the docs like: If you only want to calculate |
Also, could we add two more methods like minimum(poly, xs)
maximum(poly, xs) which returns the smallest/largest result of calling the polynomial
Then the API seems to be complete. |
Also, a small suggestion to the naming of variables: In Julia's official doc, they use extrema(f, itr; [init]) -> (mn, mx) Could we use them also? |
I have re-read the doc of extrema(f, itr) -> (mn, mx)
maximum(f, itr)
minimum(f, itr) means calling Should we respect the intention of the API? I mean, should we define function Base.maximum(poly::Polynomial, itr)
ys = poly.(iter)
return maximum(poly)
end or keep the current implementation? Sorry if I am being too picky. |
OK, it seems that if I call julia> maximum(p, 1:10)
4321
julia> @which maximum(p, 1:10)
maximum(f, a::AbstractArray; dims, kw...) in Base at reducedim.jl:995
julia> maximum(p, Tuple(1:10))
4321
julia> @which maximum(p, Tuple(1:10))
maximum(f, a; kw...) in Base at reduce.jl:698 |
Maybe if someone is going to redo these things, a
|
Thanks for all this. Still things to think about :) |
Using a different approach with just a |
Just in case this is reconsidered, perhaps |
Yeah, this PR would have done While I have your attention, the code to find the critical points first reduces the polynomial with the numeric gcd to hopefully reduce the issue with multiple roots being identified as complex values. However, that code takes some real time to compile so the first use is really sluggish. I also added a small amount of pre-compile code to reduce that, but that in turn increases the loading time. It was around 300ms, it might now be about 500ms. I wonder if that is a big price to pay for something that isn't likely to be used by most. Any thoughts on that? I personally find it un-noticable, but it is a bit of a trade off. |
Sorry I had missed this. I think adding precompile statements is a good idea, as load times should be resolved by that, now that invalidations have been dealt with. |
This is suggested in #454
It basically implements the
findmin(f, domain)
generic. (Well, not quite. With a function and iterable, it returns an index not a value. This returns the value)