-
Notifications
You must be signed in to change notification settings - Fork 11
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
Besselj and Besselk for all integer and noninteger postive orders #29
Conversation
Some general things going forward.... Do we want to have another branch for large x that would be faster at the cost of more branches? Unsure honestly. We can look more at @cgeoga large argument expansion https://github.com/heltonmc/Bessels.jl/issues/17#issuecomment-1197096678. I have one for |
That looks great! |
Hey, sorry to have disappeared for a few days! I'm about to enter a pretty swamped period and will probably be slower for the next week or so. Those times for the intermediate range are very impressive to me. I played a bit more with the Miller recurrence, but I think to get to the tolerances you are going for here it would just be too expensive. With regard to very large I like the idea of the code re-use with the With regard to extra branches, I would think there are enough ns on the table where it would make sense to have several for various specialized cases, like when |
Yes - the miller recurrence is definitely something else to consider! I think that is what Amos is using? So I would imagine that we might be able to slightly faster than what they have but similar accuracies. I do like this approach as the amount of code is much smaller and easier to optimize and diagnose branch cutoffs. But to your point. The continued fraction approach is much slower when x < 1 so we should use a power series here for sure. I'll do that now. For the large argument, I'm very happy to include that. Maybe once I merge this you could submit a PR with the large argument branch that you wrote? We can then integrate I think. I do like to lean on the asymptotic expansions just because they are so optimized (and I use them for every single routine) but definitely when x>>nu a different expansion will be better so let's do that. I think for specific nu like 1/2 it would be worth having an optimized branch. Maybe as a separate PR after sort this all out. I'm also going to work on nu =1/3 because we can use those for Airy functions which are needed as well.... but in time. |
Here is a plot of the relative errors using the uniform asymptotic expansion (with 10 U polynomials). So it looks like it converges fairly well even when nu is small. Which is quite fast (not quite as the asymptotic expansion would be for large x though) julia> @benchmark Bessels.besselk_large_orders(120.0,80.0)
BenchmarkTools.Trial: 10000 samples with 968 evaluations.
Range (min … max): 79.545 ns … 129.347 ns ┊ GC (min … max): 0.00% … 0.00%
Time (median): 79.718 ns ┊ GC (median): 0.00%
Time (mean ± σ): 80.406 ns ± 3.194 ns ┊ GC (mean ± σ): 0.00% ± 0.00%
█▃ ▃ ▁
██▅▅▅▅▅▄▅██▃▄▄▆▇▄▄▂▄▂▄▂▃▄▃▃▄▅▄▄▃▄▃▃▃▂▃▄▄▄▃▄▃▅▄▄▅▄▂▄▅▅▄▄▅▃▄▂▄ █
79.5 ns Histogram: log(frequency) by time 98.4 ns <
Memory estimate: 0 bytes, allocs estimate: 0. |
Fantastic. This code is sort of on a pedestal for me and I'm nervous to submit an un-clean PR, but I'm very happy to give it a shot. Do you have a series code already? If not, our series code is pretty decent, I think, and I could try to clean it up and submit it for consideration as well in a separate issue or something. Once this merges I'll definitely submit an asymptotic expansion PR. Do you want the exponential correction in that? The way I wrote it in our code it uses the maximum possible order, but maybe it would be faster for me to hard-code just using the leading term in it or something, if you wanted. Happy to submit it that way and potentially prune it off if the speed cost is too high. I could also add the half-integer order code from #25 in that same PR if you'd like. Also, funny about that plot---in my mind, an order of, like, |
So I'd probably pose the following branches.
With the option of a 4th branch for asymptotic x >> nu. That sounds pretty reasonable to me...? |
Definitely don't feel that way. The code is very good so I think we can just drop in and clean some stuff up! But let's plan on that when you have time submit the asymptotic expansion for large x and we can go through it. The exponential correction sounds reasonable but we should have a pretty specific cutoff where we want to use it and set the order to a specific value for the entire branch. I do not have a power series for But yes if you want half orders please go ahead and submit that (preferably as a separate PR)! Haha about the orders! In my research I deal with a lot of infinite series over the order so we need calculations for very high (nu>1e4) numbers.... |
Co-authored-by: Chris Geoga <[email protected]>
I appreciate that! I see you committed the code from the repo directly, but here is a slightly cleaned up version that I think is a slightly better style fit:
Note that the modified version in our repo is not The issue I'm having, though, is that my EDIT: the code is a bit obfuscated, so for reference I'm using equation 3.2 from here. |
Codecov Report
@@ Coverage Diff @@
## master #29 +/- ##
==========================================
+ Coverage 99.06% 99.10% +0.04%
==========================================
Files 14 14
Lines 1172 1231 +59
==========================================
+ Hits 1161 1220 +59
Misses 11 11
📣 Codecov can now indicate which changes are the most critical in Pull Requests. Learn more |
I think this is ready to merge now. I think the last thing is still the idea of potentially returning complex numbers for real (negative) inputs. Mathematica will return complex values for real inputs but julia> using SpecialFunctions
julia> bessely(1.2, -0.2)
ERROR: DomainError with -0.2:
`x` must be nonnegative.
Stacktrace:
[1] bessely(nu::Float64, x::Float64)
@ SpecialFunctions ~/.julia/packages/SpecialFunctions/NBIqR/src/bessel.jl:556
[2] top-level scope
@ REPL[2]:1
julia> bessely(1.2, -Complex(0.2))
3.8701259814738727 - 2.904048804819615im Matlab will output complex numbers for real inputs. >> bessely(1.2, -0.2)
ans =
3.8701 - 2.9040i
>> bessely(1.2, -complex(0.2))
ans =
3.8701 - 2.9040i I'm wondering what the best is here? |
Julia's general practice is to throw a domain error in cases like this (and require the input to be complex) |
That sounds good to me. One issue is that we don't actually support arguments for nonzero imaginary parts. So we would probably have to have a additional checks for nonzero complex terms. Edit: I'm wondering if that would be a little confusing to allow for complex arguments but Im(x) = 0 for now... |
Honestly, I think we can not deal with this until we start on more general complex support. |
Ya that sounds good to me.... I'll make an issue about it just so people are aware and put it on the updated readme! |
Implementation of
besselk
andbesseli
for all real orders. Still have several things to finish and touch up but these methods so far seem to workbesseli
is very straightforward. Use the uniform asymptotic expansion when eitherx
ornu
is large (x>35, nu>25) and then the power series elsewhere... now I also have the asymptotic expansion when x is large in there that is significantly faster but may just remove so the whole function has just two branches... decision point for later..besselk
is obviously not straightforward. However, the same uniform asymptotic expansion applies when eitherx
ornu
is large (x>35, nu>25) so use that. Now we could also use an asymptotic expansion with respect tox
here as well (see https://github.com/heltonmc/Bessels.jl/issues/17#issuecomment-1197096678), but again at the cost of an extra branch.So the rest comes down to what do we do when both x < 35 and nu < 25... The power series form for
besselk
is accurate when x < 2 or nu > 1.6x - 1.0 so we could use that but that doesn't help much for x > 2 and adds an extra branch. We could use numerical integration here from small orders and then use forward recurrence (see https://github.com/heltonmc/Bessels.jl/issues/17#issuecomment-1177801831) and subsequent optimizations.... We could also use the hypergeometric series (see https://github.com/heltonmc/Bessels.jl/issues/17#issuecomment-1196836506) for small orders then use a continued fraction approach to generate k_[nu+1} and then use forward recurrence to fill everything else..... we could also use some type of polynomial approximation (Chebyshev) but I found that this only worked well for a limited range.I think the simplest method is to take advantage of the fact that the
besseli
power series is very accurate for nu<25 for any x value.... the advantage is we can also generate I_{nu+1} very easily so we can get very accurate estimates of I_{nu+1}/I_{nu} for any value of nu in this range. Now, we can also use a continued fraction approach to get K_{nu+1}/K_{nu} (see https://github.com/heltonmc/Bessels.jl/issues/17#issuecomment-1195726642). So using the two ratios we can use the Wronskian to get K_{nu} very simply with a lot of code reuse.Here are some benchmarks for this method.
So almost 2.5x faster than SpecialFunctions....
O and and here is a plot of the relative errors compared to ArbNumerics.jl
With a maximum error of..
For reference this more accurate than SpecialFunctions.jl which had a maximum error of 5e-15.