ENH: stats.make_distribution: create a new distribution from an old distribution (and more)

Hi Team,

To give users access to the benefits of the new distribution infrastructure with existing SciPy distributions, gh-21707 proposes scipy.stats.make_distribution, which accepts a non-frozen distribution object (like stats.gausshyper) and returns a subclass of the new _ProbabilityDistribution class:

from scipy import stats
GH = stats.make_distribution(stats.gausshyper)
Y = GH(a=1.5, b=2.5, c=2, z=0)

For example, stats.gausshyper doesn’t define a _cdf method, so stats.gausshyper.cdf resorts to integration with integrate.quad. The new infrastructure integrates stats.gausshyper._pdf with _tanhsinh, instead. Benchmarks in the PR shows an example inspired by gh-18799; in this case, the new method is ~120x faster for vectorized evaluation, and the worst error is ~1e-13 compared to ~1e-7. These improvements find their way into most of the other public methods, since gausshyper customizes only _pdf and _munp.

This same function will be usable with discrete distributions soon. And we’ll also define an interface by which users can pass a custom class to make_distribution, making it easier to create distributions without needing to know about the new (or old) infrastructure’s private methods.

Feedback welcome! If you’re interested, please join the conversation in gh-21707.

Thanks!
Matt