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
ENH: add exact=False support for stirling2 #19287
Conversation
Thanks @rlucas7! Some quick comments/suggestions
|
I don't think the DP approach really fits the workflow of a ufunc though, IIUC every value is computed in a parallel way. Is there some point I'm missing? |
NumPy ufuncs don't employ parallelization. They just loop through and calculate values in a compiled language without any Python overhead. At one point while working on |
@steppi Looking into the DP in ufunc and I had a question In the Is there a different way to do the memory allocation or are we ok with doing it that way? |
This passes builds for me locally but seems like different looks like I can use the Other one is a linter issue from the rebase I just did to get the action to run. |
Ok, looks like the @steppi | @person142 It looks like I need to add function names to some constituent part of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a few brief comments, I haven't looked at this closely yet.
Also just a reminder, before this goes in I'd like to see
- At least the second order asymptotic expansion implemented.
- Some experiments showing how the regimes where the different implementations are used was decided.
scipy/_lib/unuran
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this change got pulled in somehow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
on the rebase-will remove
scipy/special/_add_newdocs.py
Outdated
add_newdoc("stirling2_inexact", | ||
r""" | ||
private method do not use | ||
""") | ||
|
||
|
||
add_newdoc("stirling2_lanczos", | ||
r""" | ||
private method do not use | ||
""") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Private functions in special should have a leading underscore in their names. Also, the language used for other private functions is
"Internal function, do not use."
They need to be modified in |
Not clear to me how to get access to
Yes. I will do these once I have all 3 methods implemented at C level. I plan to use inexact and a dispatch table to call out to temme/lanczos/dp by (n,k). my plan is to first implement all 3 methods as separate functions at c level and expose to python level. Doing it this way makes it easier (for me) to run the experiments for switching between the 3 approaches based on different Let me know if you've got a better way. |
I’ll post a small example when I have time later today or tomorrow.
Ok, yeah that makes sense, just mark your PR as draft until you think it’s ready to go in. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @rlucas7 , I appreciate the review request but this is way outside my expertise. I had not even heard of Stirling numbers before.
C/H files are up in top level of special and found there after build.
Once you are able to post the code for this I can finish the Temme approximation code in stirling.c. Then I'll do the experiments to determine when to switch between Lanczos/DP(doubles)/Temme and add the dispatch to |
Sorry, it's trickier than I thought because of the complexity of SciPy's build system (or maybe just my lack of familiarity). I'm not sure what changes would need to be made here to make #include "_lambertw.h"
#include "_orthogonal_eval.h" I've only done this in small projects with a setup.py build, in that case if you have an extension where the sources contain a I think the simplest thing to do is just to write stirling2 in Cython instead of C. Then you can just do from ._lambertw cimport lambertw_scalar
from ._orthogonal_eval cimport binom |
@rlucas7, long term, I think the best course of action will be to rewrite @izaid has kindly offered to help review PRs to rewrite such functions in C++. I plan to submit PRs for these two first. At the moment I think your options are to either follow through implementing the expansion in Cython, and we can convert later, or to wait until the |
@steppi thanks for letting me know. I’m not opposed to switching to c/c++ for Cython special functions but we should probably have a dev list discussion about that-unless we already have. Just to make sure everyone is on board, e.g. I think the docs still read to prefer cython for new stuff, which hasn’t really been followed strictly since I’ve been contributing.Do you have a time estimate on the lambertw and binom functions? (Just approximate timeline is all I’m asking) o/w it’s difficult for me to make the decision of cython or c. Alternately, we could keep the Temme in Python for now and move it down to c later once the cython -> c++ migrations are merged, Please let me know if you have opinions on proposed third way.Also, happy to be included in any reviews touching those functions. -Lucas Roberts On Oct 16, 2023, at 1:23 PM, Albert Steppi ***@***.***> wrote:
@rlucas7, long term, I think the best course of action will be to rewrite lambertw, binom, (and all other special functions in SciPy that are implemented in Cython) into C++. This will make it easier to call them from other functions, and also opens up the possibility of using them on the GPU in CuPy, and other array computing libraries.
@izaid has kindly offered to help review PRs to rewrite such functions in C++. I plan to submit PRs for these two first. At the moment I think your options are to either follow through implementing the expansion in Cython, and we can convert later, or to wait until the lambertw and binom have been rewritten.
—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you were mentioned.Message ID: ***@***.***>
|
@rlucas7. I plan to have PRs submitted for |
@rlucas7 while you’re blocked due to the current state of |
Sure, here you go. My decision based on looking at these is to NOT USE either Lanczos or Temme (this was a bit of a surprise for me). The DP approach is pretty accurate up through the space of values that are covered by valid doubles. Neither Lanczos (applied to inclusion-exclusion formula) nor the Temme 2nd order approximation come close in terms of accuracy. stirling2_approx_comparisons.txt Let me know if you disagree. Here's the source I used for approximations: # bring into dev env via `python dev.py python`
import numpy as np
import scipy.special as sc
def stirling2_asymptotic_order2(n, k):
n, k = np.asarray(n,dtype=float), np.asarray(k,dtype=float)
mu = k/n
delta = 1/mu * np.exp(-1/mu)
x0 = 1/mu + sc.lambertw(-delta)
t0 = (n - k) / k
F = np.sqrt(t0/((1 + t0)*(x0 - t0)))
A = -n*np.log(x0) + k*np.log(np.exp(x0) - 1) - k*t0 + (n - k)*np.log(t0)
F1 = (
-2*x0**3 + 2*t0**5 + 4*t0**3 + 4*t0**4 + 3*x0**2 * t0 - 6*x0*t0**4
- 5*x0**2 * t0**2 + 2*x0**4 * t0 + x0**3*t0 - 6*x0**3 * t0**2
+ 8*x0**2 * t0**3
) / (24*F*(1 + t0)**2*(x0 - t0)**4)
try:
result = np.exp(A)*k**(n - k)*sc.binom(n, k)*(F - F1/k)
except OverflowError:
result = np.inf
return result.real
rows = []
for n in range(3, 221): # I get an overflow error at n=220
args = ([n], list(range(1,n))) # the last value is n-1, including `n` value gives a nan
exact = sc.stirling2(*args, exact=True)
asymptotic2 = stirling2_asymptotic_order2(n=args[0], k=args[1])
rel_error = np.abs(exact - asymptotic2) / np.abs(exact)
print(f"n: {n} (Temme-2) max rel_error: {max(rel_error)}")
dp2 = sc.stirling2_dp(args[0], args[1])
rel_error = np.abs(exact - dp2) / np.abs(exact)
print(f"n: {n} (dp) max rel_error: {max(rel_error)}")
lanczos = sc.stirling2_lanczos(args[0], args[1])
rel_error = np.abs(exact - lanczos) / np.abs(exact)
print(f"n: {n} (Lanczos) max rel_error: {max(rel_error)}")
# we also have errors on the dp approx and on the Lanczos approx that has some changes I'm going to push now (nothing substantive changed from previous commit though). |
@rlucas7, we need to take into account time and memory efficiency for |
I've been working on timings as a follow up to this comment. When we compare only the approximate versions of DP and temme, the runtime of the approximate versions do start to scale linearly with so for the dispatch I'll use from scipy.special import stirling2_dp, stirling2_temme, stirling2
import matplotlib.pyplot as plt
import timeit
y_temme = []
y_dp = []
x = list(range(10, 105, 5))
for n in x:
s_temme = f"""n={n}; stirling2_temme([{n}], list(range(1,{n})))"""
y_temme.append(timeit.timeit(stmt=s_temme, number=10000, setup="from scipy.special import stirling2_temme"))
s_dp = f"""n={n}; stirling2_dp([{n}], list(range(1,{n})))"""
y_dp.append(timeit.timeit(stmt=s_dp, number=10000, setup="from scipy.special import stirling2_dp"))
fig, ax = plt.subplots()
line1, = ax.plot(x, y_temme, linewidth=2.0, label="Temme")
line2, = ax.plot(x, y_dp, linewidth=2.0, label="DP")
ax.legend(handles=[line1, line2])
plt.title(label="Runtimes")
plt.show() ^ code used to generate the figure |
Looks like the _lambertw.h code isn't getting picked up in the build process on github, this works for me locally, @steppi is there something that looks incorrect here to you? |
* adding preliminary support for exact=False support to stirling2 for now only adds partial Temme and no Lanczos b/c dp works well for small N and Lancozs has overflow issuesfor N>32. add sitrling2_inexact dp ufunc add stirling2_lanczos c version fix dtype interactive rebase error move c code out of cephes to top level of special add more cleanup code attempt cxx move inline stirling2 for g++ and other related changes cleanup and apply horner
I'm seeing
both locally and in the test harness, I'm not sure how we handle this with the c++ setup here in special, @steppi have you seen this before? (any ideas?) |
unsigned int dtype isn't supported in the ufunc infrastructure. These are the allowed typecodes in
I think just get rid of that test for |
Nevermind that, the test has 64 bit unsigned longs. I think the |
The build fails for me locally if I swap |
Should we add an input validation and raise a |
Yeah. And if we do that we can make the ufunc take doubles and do input validation and convert to double before passing. I think the overhead should be negligible. |
Ok I think I understand, with this way we can handle the uint cases too. This means that there is no need to remove the If no major issues from test harness-I'll change from draft status tomorrow. |
I'm a few minutes before tomorrow (20 or so) but everything is green in the test harness. @steppi I think this is ready for a closer scrutiny. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice job getting this working. Still needs some cleaning up, but nothing too major.
Nice work @rlucas7! Thanks for all your patience here. |
Reference issue
Adds preliminary
exact=False
support for Stirling numbers of the second kind#17890
What does this implement/fix?
adding preliminary support for exact=False support to stirling2
Additional information
@steppi During review from prior PR we decided to include
exact=False
support.This PR adds initial support for inclusion-exclusion via Lanczos approximation.
Still needs to add:
n > 33
and other places where it is more accurate than Lanczos