scipy 1.15.3__cp313-cp313-musllinux_1_2_aarch64.whl → 1.16.0rc2__cp313-cp313-musllinux_1_2_aarch64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scipy/__config__.py +10 -10
- scipy/__init__.py +3 -6
- scipy/_cyutility.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/_lib/_array_api.py +486 -161
- scipy/_lib/_array_api_compat_vendor.py +9 -0
- scipy/_lib/_bunch.py +4 -0
- scipy/_lib/_ccallback_c.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/_lib/_docscrape.py +1 -1
- scipy/_lib/_elementwise_iterative_method.py +15 -26
- scipy/_lib/_fpumode.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/_lib/_sparse.py +41 -0
- scipy/_lib/_test_ccallback.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/_lib/_test_deprecation_call.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/_lib/_test_deprecation_def.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/_lib/_testutils.py +6 -2
- scipy/_lib/_uarray/_uarray.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/_lib/_util.py +222 -125
- scipy/_lib/array_api_compat/__init__.py +4 -4
- scipy/_lib/array_api_compat/_internal.py +19 -6
- scipy/_lib/array_api_compat/common/__init__.py +1 -1
- scipy/_lib/array_api_compat/common/_aliases.py +365 -193
- scipy/_lib/array_api_compat/common/_fft.py +94 -64
- scipy/_lib/array_api_compat/common/_helpers.py +413 -180
- scipy/_lib/array_api_compat/common/_linalg.py +116 -40
- scipy/_lib/array_api_compat/common/_typing.py +179 -10
- scipy/_lib/array_api_compat/cupy/__init__.py +1 -4
- scipy/_lib/array_api_compat/cupy/_aliases.py +61 -41
- scipy/_lib/array_api_compat/cupy/_info.py +16 -6
- scipy/_lib/array_api_compat/cupy/_typing.py +24 -39
- scipy/_lib/array_api_compat/dask/array/__init__.py +6 -3
- scipy/_lib/array_api_compat/dask/array/_aliases.py +267 -108
- scipy/_lib/array_api_compat/dask/array/_info.py +105 -34
- scipy/_lib/array_api_compat/dask/array/fft.py +5 -8
- scipy/_lib/array_api_compat/dask/array/linalg.py +21 -22
- scipy/_lib/array_api_compat/numpy/__init__.py +13 -15
- scipy/_lib/array_api_compat/numpy/_aliases.py +98 -49
- scipy/_lib/array_api_compat/numpy/_info.py +36 -16
- scipy/_lib/array_api_compat/numpy/_typing.py +27 -43
- scipy/_lib/array_api_compat/numpy/fft.py +11 -5
- scipy/_lib/array_api_compat/numpy/linalg.py +75 -22
- scipy/_lib/array_api_compat/torch/__init__.py +3 -5
- scipy/_lib/array_api_compat/torch/_aliases.py +262 -159
- scipy/_lib/array_api_compat/torch/_info.py +27 -16
- scipy/_lib/array_api_compat/torch/_typing.py +3 -0
- scipy/_lib/array_api_compat/torch/fft.py +17 -18
- scipy/_lib/array_api_compat/torch/linalg.py +16 -16
- scipy/_lib/array_api_extra/__init__.py +26 -3
- scipy/_lib/array_api_extra/_delegation.py +171 -0
- scipy/_lib/array_api_extra/_lib/__init__.py +1 -0
- scipy/_lib/array_api_extra/_lib/_at.py +463 -0
- scipy/_lib/array_api_extra/_lib/_backends.py +46 -0
- scipy/_lib/array_api_extra/_lib/_funcs.py +937 -0
- scipy/_lib/array_api_extra/_lib/_lazy.py +357 -0
- scipy/_lib/array_api_extra/_lib/_testing.py +278 -0
- scipy/_lib/array_api_extra/_lib/_utils/__init__.py +1 -0
- scipy/_lib/array_api_extra/_lib/_utils/_compat.py +74 -0
- scipy/_lib/array_api_extra/_lib/_utils/_compat.pyi +45 -0
- scipy/_lib/array_api_extra/_lib/_utils/_helpers.py +559 -0
- scipy/_lib/array_api_extra/_lib/_utils/_typing.py +10 -0
- scipy/_lib/array_api_extra/_lib/_utils/_typing.pyi +105 -0
- scipy/_lib/array_api_extra/testing.py +359 -0
- scipy/_lib/decorator.py +2 -2
- scipy/_lib/doccer.py +1 -7
- scipy/_lib/messagestream.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/_lib/pyprima/__init__.py +212 -0
- scipy/_lib/pyprima/cobyla/__init__.py +0 -0
- scipy/_lib/pyprima/cobyla/cobyla.py +559 -0
- scipy/_lib/pyprima/cobyla/cobylb.py +714 -0
- scipy/_lib/pyprima/cobyla/geometry.py +226 -0
- scipy/_lib/pyprima/cobyla/initialize.py +215 -0
- scipy/_lib/pyprima/cobyla/trustregion.py +492 -0
- scipy/_lib/pyprima/cobyla/update.py +289 -0
- scipy/_lib/pyprima/common/__init__.py +0 -0
- scipy/_lib/pyprima/common/_bounds.py +34 -0
- scipy/_lib/pyprima/common/_linear_constraints.py +46 -0
- scipy/_lib/pyprima/common/_nonlinear_constraints.py +54 -0
- scipy/_lib/pyprima/common/_project.py +173 -0
- scipy/_lib/pyprima/common/checkbreak.py +93 -0
- scipy/_lib/pyprima/common/consts.py +47 -0
- scipy/_lib/pyprima/common/evaluate.py +99 -0
- scipy/_lib/pyprima/common/history.py +38 -0
- scipy/_lib/pyprima/common/infos.py +30 -0
- scipy/_lib/pyprima/common/linalg.py +435 -0
- scipy/_lib/pyprima/common/message.py +290 -0
- scipy/_lib/pyprima/common/powalg.py +131 -0
- scipy/_lib/pyprima/common/preproc.py +277 -0
- scipy/_lib/pyprima/common/present.py +5 -0
- scipy/_lib/pyprima/common/ratio.py +54 -0
- scipy/_lib/pyprima/common/redrho.py +47 -0
- scipy/_lib/pyprima/common/selectx.py +296 -0
- scipy/_lib/tests/test__util.py +105 -121
- scipy/_lib/tests/test_array_api.py +166 -35
- scipy/_lib/tests/test_bunch.py +7 -0
- scipy/_lib/tests/test_ccallback.py +2 -10
- scipy/_lib/tests/test_public_api.py +13 -0
- scipy/cluster/_hierarchy.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/cluster/_optimal_leaf_ordering.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/cluster/_vq.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/cluster/hierarchy.py +393 -223
- scipy/cluster/tests/test_hierarchy.py +273 -335
- scipy/cluster/tests/test_vq.py +45 -61
- scipy/cluster/vq.py +39 -35
- scipy/conftest.py +263 -157
- scipy/constants/_constants.py +4 -1
- scipy/constants/tests/test_codata.py +2 -2
- scipy/constants/tests/test_constants.py +11 -18
- scipy/datasets/_download_all.py +15 -1
- scipy/datasets/_fetchers.py +7 -1
- scipy/datasets/_utils.py +1 -1
- scipy/differentiate/_differentiate.py +25 -25
- scipy/differentiate/tests/test_differentiate.py +24 -25
- scipy/fft/_basic.py +20 -0
- scipy/fft/_helper.py +3 -34
- scipy/fft/_pocketfft/helper.py +29 -1
- scipy/fft/_pocketfft/pypocketfft.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/fft/_pocketfft/tests/test_basic.py +2 -4
- scipy/fft/_pocketfft/tests/test_real_transforms.py +4 -4
- scipy/fft/_realtransforms.py +13 -0
- scipy/fft/tests/test_basic.py +27 -25
- scipy/fft/tests/test_fftlog.py +16 -7
- scipy/fft/tests/test_helper.py +18 -34
- scipy/fft/tests/test_real_transforms.py +8 -10
- scipy/fftpack/convolve.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/fftpack/tests/test_basic.py +2 -4
- scipy/fftpack/tests/test_real_transforms.py +8 -9
- scipy/integrate/_bvp.py +9 -3
- scipy/integrate/_cubature.py +3 -2
- scipy/integrate/_dop.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/integrate/_lsoda.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/integrate/_ode.py +9 -2
- scipy/integrate/_odepack.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/integrate/_quad_vec.py +21 -29
- scipy/integrate/_quadpack.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/integrate/_quadpack_py.py +11 -7
- scipy/integrate/_quadrature.py +3 -3
- scipy/integrate/_rules/_base.py +2 -2
- scipy/integrate/_tanhsinh.py +48 -47
- scipy/integrate/_test_multivariate.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/integrate/_test_odeint_banded.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/integrate/_vode.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/integrate/tests/test__quad_vec.py +0 -6
- scipy/integrate/tests/test_banded_ode_solvers.py +85 -0
- scipy/integrate/tests/test_cubature.py +21 -35
- scipy/integrate/tests/test_quadrature.py +6 -8
- scipy/integrate/tests/test_tanhsinh.py +56 -48
- scipy/interpolate/__init__.py +70 -58
- scipy/interpolate/_bary_rational.py +22 -22
- scipy/interpolate/_bsplines.py +119 -66
- scipy/interpolate/_cubic.py +65 -50
- scipy/interpolate/_dfitpack.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/interpolate/_dierckx.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/interpolate/_fitpack.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/interpolate/_fitpack2.py +9 -6
- scipy/interpolate/_fitpack_impl.py +32 -26
- scipy/interpolate/_fitpack_repro.py +23 -19
- scipy/interpolate/_interpnd.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/interpolate/_interpolate.py +30 -12
- scipy/interpolate/_ndbspline.py +13 -18
- scipy/interpolate/_ndgriddata.py +5 -8
- scipy/interpolate/_polyint.py +95 -31
- scipy/interpolate/_ppoly.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/interpolate/_rbf.py +2 -2
- scipy/interpolate/_rbfinterp.py +1 -1
- scipy/interpolate/_rbfinterp_pythran.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/interpolate/_rgi.py +31 -26
- scipy/interpolate/_rgi_cython.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/interpolate/dfitpack.py +0 -20
- scipy/interpolate/interpnd.py +1 -2
- scipy/interpolate/tests/test_bary_rational.py +2 -2
- scipy/interpolate/tests/test_bsplines.py +97 -1
- scipy/interpolate/tests/test_fitpack2.py +39 -1
- scipy/interpolate/tests/test_interpnd.py +32 -20
- scipy/interpolate/tests/test_interpolate.py +48 -4
- scipy/interpolate/tests/test_rgi.py +2 -1
- scipy/io/_fast_matrix_market/__init__.py +2 -0
- scipy/io/_fast_matrix_market/_fmm_core.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/io/_harwell_boeing/_fortran_format_parser.py +19 -16
- scipy/io/_harwell_boeing/hb.py +7 -11
- scipy/io/_idl.py +5 -7
- scipy/io/_netcdf.py +15 -5
- scipy/io/_test_fortran.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/io/arff/tests/test_arffread.py +3 -3
- scipy/io/matlab/__init__.py +5 -3
- scipy/io/matlab/_mio.py +4 -1
- scipy/io/matlab/_mio5.py +19 -13
- scipy/io/matlab/_mio5_utils.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/io/matlab/_mio_utils.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/io/matlab/_miobase.py +4 -1
- scipy/io/matlab/_streams.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/io/matlab/tests/test_mio.py +46 -18
- scipy/io/matlab/tests/test_mio_funcs.py +1 -1
- scipy/io/tests/test_mmio.py +7 -1
- scipy/io/tests/test_wavfile.py +41 -0
- scipy/io/wavfile.py +57 -10
- scipy/linalg/_basic.py +113 -86
- scipy/linalg/_cythonized_array_utils.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/linalg/_decomp.py +22 -9
- scipy/linalg/_decomp_cholesky.py +28 -13
- scipy/linalg/_decomp_cossin.py +45 -30
- scipy/linalg/_decomp_interpolative.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/linalg/_decomp_ldl.py +4 -1
- scipy/linalg/_decomp_lu.py +18 -6
- scipy/linalg/_decomp_lu_cython.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/linalg/_decomp_polar.py +2 -0
- scipy/linalg/_decomp_qr.py +6 -2
- scipy/linalg/_decomp_qz.py +3 -0
- scipy/linalg/_decomp_schur.py +3 -1
- scipy/linalg/_decomp_svd.py +13 -2
- scipy/linalg/_decomp_update.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/linalg/_expm_frechet.py +4 -0
- scipy/linalg/_fblas.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/linalg/_flapack.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/linalg/_linalg_pythran.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/linalg/_matfuncs.py +187 -4
- scipy/linalg/_matfuncs_expm.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/linalg/_matfuncs_schur_sqrtm.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/linalg/_matfuncs_sqrtm.py +1 -99
- scipy/linalg/_matfuncs_sqrtm_triu.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/linalg/_procrustes.py +2 -0
- scipy/linalg/_sketches.py +17 -6
- scipy/linalg/_solve_toeplitz.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/linalg/_solvers.py +7 -2
- scipy/linalg/_special_matrices.py +26 -36
- scipy/linalg/cython_blas.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/linalg/cython_lapack.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/linalg/lapack.py +22 -2
- scipy/linalg/tests/_cython_examples/meson.build +7 -0
- scipy/linalg/tests/test_basic.py +31 -16
- scipy/linalg/tests/test_batch.py +588 -0
- scipy/linalg/tests/test_cythonized_array_utils.py +0 -2
- scipy/linalg/tests/test_decomp.py +40 -3
- scipy/linalg/tests/test_decomp_cossin.py +14 -0
- scipy/linalg/tests/test_decomp_ldl.py +1 -1
- scipy/linalg/tests/test_lapack.py +115 -7
- scipy/linalg/tests/test_matfuncs.py +157 -102
- scipy/linalg/tests/test_procrustes.py +0 -7
- scipy/linalg/tests/test_solve_toeplitz.py +1 -1
- scipy/linalg/tests/test_special_matrices.py +1 -5
- scipy/ndimage/__init__.py +1 -0
- scipy/ndimage/_ctest.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/ndimage/_cytest.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/ndimage/_delegators.py +8 -2
- scipy/ndimage/_filters.py +453 -5
- scipy/ndimage/_interpolation.py +36 -6
- scipy/ndimage/_measurements.py +4 -2
- scipy/ndimage/_morphology.py +5 -0
- scipy/ndimage/_nd_image.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/ndimage/_ni_docstrings.py +5 -1
- scipy/ndimage/_ni_label.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/ndimage/_ni_support.py +1 -5
- scipy/ndimage/_rank_filter_1d.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/ndimage/_support_alternative_backends.py +18 -6
- scipy/ndimage/tests/test_filters.py +370 -259
- scipy/ndimage/tests/test_fourier.py +7 -9
- scipy/ndimage/tests/test_interpolation.py +68 -61
- scipy/ndimage/tests/test_measurements.py +18 -35
- scipy/ndimage/tests/test_morphology.py +143 -131
- scipy/ndimage/tests/test_splines.py +1 -3
- scipy/odr/__odrpack.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/optimize/_basinhopping.py +13 -7
- scipy/optimize/_bglu_dense.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/optimize/_bracket.py +17 -24
- scipy/optimize/_chandrupatla.py +9 -10
- scipy/optimize/_cobyla_py.py +104 -123
- scipy/optimize/_constraints.py +14 -10
- scipy/optimize/_differentiable_functions.py +371 -230
- scipy/optimize/_differentialevolution.py +4 -3
- scipy/optimize/_direct.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/optimize/_dual_annealing.py +1 -1
- scipy/optimize/_elementwise.py +1 -4
- scipy/optimize/_group_columns.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/optimize/_highspy/_core.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/optimize/_highspy/_highs_options.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/optimize/_lbfgsb.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/optimize/_lbfgsb_py.py +57 -16
- scipy/optimize/_linprog_doc.py +2 -2
- scipy/optimize/_linprog_highs.py +2 -2
- scipy/optimize/_linprog_ip.py +25 -10
- scipy/optimize/_linprog_util.py +14 -16
- scipy/optimize/_lsap.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/optimize/_lsq/common.py +3 -3
- scipy/optimize/_lsq/dogbox.py +16 -2
- scipy/optimize/_lsq/givens_elimination.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/optimize/_lsq/least_squares.py +198 -126
- scipy/optimize/_lsq/lsq_linear.py +6 -6
- scipy/optimize/_lsq/trf.py +35 -8
- scipy/optimize/_milp.py +3 -1
- scipy/optimize/_minimize.py +105 -36
- scipy/optimize/_minpack.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/optimize/_minpack_py.py +21 -14
- scipy/optimize/_moduleTNC.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/optimize/_nnls.py +20 -21
- scipy/optimize/_nonlin.py +34 -3
- scipy/optimize/_numdiff.py +288 -110
- scipy/optimize/_optimize.py +86 -48
- scipy/optimize/_pava_pybind.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/optimize/_remove_redundancy.py +5 -5
- scipy/optimize/_root_scalar.py +1 -1
- scipy/optimize/_shgo.py +6 -0
- scipy/optimize/_shgo_lib/_complex.py +1 -1
- scipy/optimize/_slsqp_py.py +216 -124
- scipy/optimize/_slsqplib.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/optimize/_spectral.py +1 -1
- scipy/optimize/_tnc.py +8 -1
- scipy/optimize/_trlib/_trlib.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/optimize/_trustregion.py +20 -6
- scipy/optimize/_trustregion_constr/canonical_constraint.py +7 -7
- scipy/optimize/_trustregion_constr/equality_constrained_sqp.py +1 -1
- scipy/optimize/_trustregion_constr/minimize_trustregion_constr.py +11 -3
- scipy/optimize/_trustregion_constr/projections.py +12 -8
- scipy/optimize/_trustregion_constr/qp_subproblem.py +9 -9
- scipy/optimize/_trustregion_constr/tests/test_projections.py +7 -7
- scipy/optimize/_trustregion_constr/tests/test_qp_subproblem.py +77 -77
- scipy/optimize/_trustregion_constr/tr_interior_point.py +5 -5
- scipy/optimize/_trustregion_exact.py +0 -1
- scipy/optimize/_zeros.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/optimize/_zeros_py.py +97 -17
- scipy/optimize/cython_optimize/_zeros.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/optimize/slsqp.py +0 -1
- scipy/optimize/tests/test__basinhopping.py +1 -1
- scipy/optimize/tests/test__differential_evolution.py +4 -4
- scipy/optimize/tests/test__linprog_clean_inputs.py +5 -3
- scipy/optimize/tests/test__numdiff.py +66 -22
- scipy/optimize/tests/test__remove_redundancy.py +2 -2
- scipy/optimize/tests/test__shgo.py +9 -1
- scipy/optimize/tests/test_bracket.py +36 -46
- scipy/optimize/tests/test_chandrupatla.py +133 -135
- scipy/optimize/tests/test_cobyla.py +74 -45
- scipy/optimize/tests/test_constraints.py +1 -1
- scipy/optimize/tests/test_differentiable_functions.py +226 -6
- scipy/optimize/tests/test_lbfgsb_hessinv.py +22 -0
- scipy/optimize/tests/test_least_squares.py +125 -13
- scipy/optimize/tests/test_linear_assignment.py +3 -3
- scipy/optimize/tests/test_linprog.py +3 -3
- scipy/optimize/tests/test_lsq_linear.py +6 -6
- scipy/optimize/tests/test_minimize_constrained.py +2 -2
- scipy/optimize/tests/test_minpack.py +4 -4
- scipy/optimize/tests/test_nnls.py +43 -3
- scipy/optimize/tests/test_nonlin.py +36 -0
- scipy/optimize/tests/test_optimize.py +95 -17
- scipy/optimize/tests/test_slsqp.py +36 -4
- scipy/optimize/tests/test_zeros.py +34 -1
- scipy/signal/__init__.py +12 -23
- scipy/signal/_delegators.py +568 -0
- scipy/signal/_filter_design.py +459 -241
- scipy/signal/_fir_filter_design.py +262 -90
- scipy/signal/_lti_conversion.py +3 -2
- scipy/signal/_ltisys.py +118 -91
- scipy/signal/_max_len_seq_inner.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/signal/_peak_finding_utils.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/signal/_polyutils.py +172 -0
- scipy/signal/_short_time_fft.py +519 -70
- scipy/signal/_signal_api.py +30 -0
- scipy/signal/_signaltools.py +719 -399
- scipy/signal/_sigtools.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/signal/_sosfilt.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/signal/_spectral_py.py +230 -50
- scipy/signal/_spline.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/signal/_spline_filters.py +108 -68
- scipy/signal/_support_alternative_backends.py +73 -0
- scipy/signal/_upfirdn.py +4 -1
- scipy/signal/_upfirdn_apply.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/signal/_waveforms.py +2 -11
- scipy/signal/_wavelets.py +1 -1
- scipy/signal/fir_filter_design.py +1 -0
- scipy/signal/spline.py +4 -11
- scipy/signal/tests/_scipy_spectral_test_shim.py +2 -171
- scipy/signal/tests/test_bsplines.py +114 -79
- scipy/signal/tests/test_cont2discrete.py +9 -2
- scipy/signal/tests/test_filter_design.py +721 -481
- scipy/signal/tests/test_fir_filter_design.py +332 -140
- scipy/signal/tests/test_savitzky_golay.py +4 -3
- scipy/signal/tests/test_short_time_fft.py +221 -3
- scipy/signal/tests/test_signaltools.py +2144 -1348
- scipy/signal/tests/test_spectral.py +50 -6
- scipy/signal/tests/test_splines.py +161 -96
- scipy/signal/tests/test_upfirdn.py +84 -50
- scipy/signal/tests/test_waveforms.py +20 -0
- scipy/signal/tests/test_windows.py +607 -466
- scipy/signal/windows/_windows.py +287 -148
- scipy/sparse/__init__.py +23 -4
- scipy/sparse/_base.py +270 -108
- scipy/sparse/_bsr.py +7 -4
- scipy/sparse/_compressed.py +59 -231
- scipy/sparse/_construct.py +90 -38
- scipy/sparse/_coo.py +115 -181
- scipy/sparse/_csc.py +4 -4
- scipy/sparse/_csparsetools.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/sparse/_csr.py +2 -2
- scipy/sparse/_data.py +48 -48
- scipy/sparse/_dia.py +105 -18
- scipy/sparse/_dok.py +0 -23
- scipy/sparse/_index.py +4 -4
- scipy/sparse/_matrix.py +23 -0
- scipy/sparse/_sparsetools.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/sparse/_sputils.py +37 -22
- scipy/sparse/base.py +0 -9
- scipy/sparse/bsr.py +0 -14
- scipy/sparse/compressed.py +0 -23
- scipy/sparse/construct.py +0 -6
- scipy/sparse/coo.py +0 -14
- scipy/sparse/csc.py +0 -3
- scipy/sparse/csgraph/_flow.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/sparse/csgraph/_matching.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/sparse/csgraph/_min_spanning_tree.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/sparse/csgraph/_reordering.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/sparse/csgraph/_shortest_path.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/sparse/csgraph/_tools.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/sparse/csgraph/_traversal.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/sparse/csgraph/tests/test_matching.py +14 -2
- scipy/sparse/csgraph/tests/test_pydata_sparse.py +4 -1
- scipy/sparse/csgraph/tests/test_shortest_path.py +83 -27
- scipy/sparse/csr.py +0 -5
- scipy/sparse/data.py +1 -6
- scipy/sparse/dia.py +0 -7
- scipy/sparse/dok.py +0 -10
- scipy/sparse/linalg/_dsolve/_superlu.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/sparse/linalg/_dsolve/linsolve.py +9 -0
- scipy/sparse/linalg/_dsolve/tests/test_linsolve.py +35 -28
- scipy/sparse/linalg/_eigen/arpack/_arpack.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/sparse/linalg/_eigen/arpack/arpack.py +23 -17
- scipy/sparse/linalg/_eigen/lobpcg/lobpcg.py +6 -6
- scipy/sparse/linalg/_interface.py +17 -18
- scipy/sparse/linalg/_isolve/_gcrotmk.py +4 -4
- scipy/sparse/linalg/_isolve/iterative.py +51 -45
- scipy/sparse/linalg/_isolve/lgmres.py +6 -6
- scipy/sparse/linalg/_isolve/minres.py +5 -5
- scipy/sparse/linalg/_isolve/tfqmr.py +7 -7
- scipy/sparse/linalg/_isolve/utils.py +2 -8
- scipy/sparse/linalg/_matfuncs.py +1 -1
- scipy/sparse/linalg/_norm.py +1 -1
- scipy/sparse/linalg/_propack/_cpropack.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/sparse/linalg/_propack/_dpropack.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/sparse/linalg/_propack/_spropack.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/sparse/linalg/_propack/_zpropack.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/sparse/linalg/_special_sparse_arrays.py +39 -38
- scipy/sparse/linalg/tests/test_pydata_sparse.py +14 -0
- scipy/sparse/tests/test_arithmetic1d.py +5 -2
- scipy/sparse/tests/test_base.py +214 -42
- scipy/sparse/tests/test_common1d.py +7 -7
- scipy/sparse/tests/test_construct.py +1 -1
- scipy/sparse/tests/test_coo.py +272 -4
- scipy/sparse/tests/test_sparsetools.py +5 -0
- scipy/sparse/tests/test_sputils.py +36 -7
- scipy/spatial/_ckdtree.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/spatial/_distance_pybind.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/spatial/_distance_wrap.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/spatial/_hausdorff.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/spatial/_qhull.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/spatial/_voronoi.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/spatial/distance.py +49 -42
- scipy/spatial/tests/test_distance.py +15 -1
- scipy/spatial/tests/test_kdtree.py +1 -0
- scipy/spatial/tests/test_qhull.py +7 -2
- scipy/spatial/transform/__init__.py +5 -3
- scipy/spatial/transform/_rigid_transform.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/spatial/transform/_rotation.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/spatial/transform/tests/test_rigid_transform.py +1221 -0
- scipy/spatial/transform/tests/test_rotation.py +1213 -832
- scipy/spatial/transform/tests/test_rotation_groups.py +3 -3
- scipy/spatial/transform/tests/test_rotation_spline.py +29 -8
- scipy/special/__init__.py +1 -47
- scipy/special/_add_newdocs.py +34 -772
- scipy/special/_basic.py +22 -25
- scipy/special/_comb.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/special/_ellip_harm_2.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/special/_gufuncs.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/special/_logsumexp.py +67 -58
- scipy/special/_orthogonal.pyi +1 -1
- scipy/special/_specfun.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/special/_special_ufuncs.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/special/_spherical_bessel.py +4 -4
- scipy/special/_support_alternative_backends.py +212 -119
- scipy/special/_test_internal.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/special/_testutils.py +4 -4
- scipy/special/_ufuncs.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/special/_ufuncs.pyi +1 -0
- scipy/special/_ufuncs.pyx +215 -1400
- scipy/special/_ufuncs_cxx.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/special/_ufuncs_cxx.pxd +2 -15
- scipy/special/_ufuncs_cxx.pyx +5 -44
- scipy/special/_ufuncs_cxx_defs.h +2 -16
- scipy/special/_ufuncs_defs.h +0 -8
- scipy/special/cython_special.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/special/cython_special.pxd +1 -1
- scipy/special/tests/_cython_examples/meson.build +10 -1
- scipy/special/tests/test_basic.py +153 -20
- scipy/special/tests/test_boost_ufuncs.py +3 -0
- scipy/special/tests/test_cdflib.py +35 -11
- scipy/special/tests/test_gammainc.py +16 -0
- scipy/special/tests/test_hyp2f1.py +2 -2
- scipy/special/tests/test_log1mexp.py +85 -0
- scipy/special/tests/test_logsumexp.py +206 -64
- scipy/special/tests/test_mpmath.py +1 -0
- scipy/special/tests/test_nan_inputs.py +1 -1
- scipy/special/tests/test_orthogonal.py +17 -18
- scipy/special/tests/test_sf_error.py +3 -2
- scipy/special/tests/test_sph_harm.py +6 -7
- scipy/special/tests/test_support_alternative_backends.py +211 -76
- scipy/stats/__init__.py +4 -1
- scipy/stats/_ansari_swilk_statistics.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/stats/_axis_nan_policy.py +5 -12
- scipy/stats/_biasedurn.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/stats/_continued_fraction.py +387 -0
- scipy/stats/_continuous_distns.py +277 -310
- scipy/stats/_correlation.py +1 -1
- scipy/stats/_covariance.py +6 -3
- scipy/stats/_discrete_distns.py +39 -32
- scipy/stats/_distn_infrastructure.py +39 -12
- scipy/stats/_distribution_infrastructure.py +900 -238
- scipy/stats/_entropy.py +9 -10
- scipy/{_lib → stats}/_finite_differences.py +1 -1
- scipy/stats/_hypotests.py +83 -50
- scipy/stats/_kde.py +53 -49
- scipy/stats/_ksstats.py +1 -1
- scipy/stats/_levy_stable/__init__.py +7 -15
- scipy/stats/_levy_stable/levyst.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/stats/_morestats.py +118 -73
- scipy/stats/_mstats_basic.py +13 -17
- scipy/stats/_mstats_extras.py +8 -8
- scipy/stats/_multivariate.py +89 -113
- scipy/stats/_new_distributions.py +97 -20
- scipy/stats/_page_trend_test.py +12 -5
- scipy/stats/_probability_distribution.py +265 -43
- scipy/stats/_qmc.py +14 -9
- scipy/stats/_qmc_cy.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/stats/_qmvnt.py +16 -95
- scipy/stats/_qmvnt_cy.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/stats/_quantile.py +335 -0
- scipy/stats/_rcont/rcont.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/stats/_resampling.py +4 -29
- scipy/stats/_sampling.py +1 -1
- scipy/stats/_sobol.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/stats/_stats.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/stats/_stats_mstats_common.py +21 -2
- scipy/stats/_stats_py.py +550 -476
- scipy/stats/_stats_pythran.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/stats/_unuran/unuran_wrapper.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/stats/_unuran/unuran_wrapper.pyi +2 -1
- scipy/stats/_variation.py +6 -8
- scipy/stats/_wilcoxon.py +13 -7
- scipy/stats/tests/common_tests.py +6 -4
- scipy/stats/tests/test_axis_nan_policy.py +62 -24
- scipy/stats/tests/test_continued_fraction.py +173 -0
- scipy/stats/tests/test_continuous.py +379 -60
- scipy/stats/tests/test_continuous_basic.py +18 -12
- scipy/stats/tests/test_discrete_basic.py +14 -8
- scipy/stats/tests/test_discrete_distns.py +16 -16
- scipy/stats/tests/test_distributions.py +95 -75
- scipy/stats/tests/test_entropy.py +40 -48
- scipy/stats/tests/test_fit.py +4 -3
- scipy/stats/tests/test_hypotests.py +153 -24
- scipy/stats/tests/test_kdeoth.py +109 -41
- scipy/stats/tests/test_marray.py +289 -0
- scipy/stats/tests/test_morestats.py +79 -47
- scipy/stats/tests/test_mstats_basic.py +3 -3
- scipy/stats/tests/test_multivariate.py +434 -83
- scipy/stats/tests/test_qmc.py +13 -10
- scipy/stats/tests/test_quantile.py +199 -0
- scipy/stats/tests/test_rank.py +119 -112
- scipy/stats/tests/test_resampling.py +47 -56
- scipy/stats/tests/test_sampling.py +9 -4
- scipy/stats/tests/test_stats.py +799 -939
- scipy/stats/tests/test_variation.py +8 -6
- scipy/version.py +2 -2
- {scipy-1.15.3.dist-info → scipy-1.16.0rc2.dist-info}/LICENSE.txt +4 -4
- {scipy-1.15.3.dist-info → scipy-1.16.0rc2.dist-info}/METADATA +11 -11
- {scipy-1.15.3.dist-info → scipy-1.16.0rc2.dist-info}/RECORD +1262 -1269
- scipy.libs/libgcc_s-69c45f16.so.1 +0 -0
- scipy.libs/libgfortran-db0b6589.so.5.0.0 +0 -0
- scipy.libs/{libstdc++-1b614e01.so.6.0.32 → libstdc++-1f1a71be.so.6.0.33} +0 -0
- scipy/_lib/array_api_extra/_funcs.py +0 -484
- scipy/_lib/array_api_extra/_typing.py +0 -8
- scipy/interpolate/_bspl.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/optimize/_cobyla.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/optimize/_cython_nnls.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/optimize/_slsqp.cpython-313-aarch64-linux-musl.so +0 -0
- scipy/spatial/qhull_src/COPYING.txt +0 -38
- scipy/special/libsf_error_state.so +0 -0
- scipy/special/tests/test_log_softmax.py +0 -109
- scipy/special/tests/test_xsf_cuda.py +0 -114
- scipy/special/xsf/binom.h +0 -89
- scipy/special/xsf/cdflib.h +0 -100
- scipy/special/xsf/cephes/airy.h +0 -307
- scipy/special/xsf/cephes/besselpoly.h +0 -51
- scipy/special/xsf/cephes/beta.h +0 -257
- scipy/special/xsf/cephes/cbrt.h +0 -131
- scipy/special/xsf/cephes/chbevl.h +0 -85
- scipy/special/xsf/cephes/chdtr.h +0 -193
- scipy/special/xsf/cephes/const.h +0 -87
- scipy/special/xsf/cephes/ellie.h +0 -293
- scipy/special/xsf/cephes/ellik.h +0 -251
- scipy/special/xsf/cephes/ellpe.h +0 -107
- scipy/special/xsf/cephes/ellpk.h +0 -117
- scipy/special/xsf/cephes/expn.h +0 -260
- scipy/special/xsf/cephes/gamma.h +0 -398
- scipy/special/xsf/cephes/hyp2f1.h +0 -596
- scipy/special/xsf/cephes/hyperg.h +0 -361
- scipy/special/xsf/cephes/i0.h +0 -149
- scipy/special/xsf/cephes/i1.h +0 -158
- scipy/special/xsf/cephes/igam.h +0 -421
- scipy/special/xsf/cephes/igam_asymp_coeff.h +0 -195
- scipy/special/xsf/cephes/igami.h +0 -313
- scipy/special/xsf/cephes/j0.h +0 -225
- scipy/special/xsf/cephes/j1.h +0 -198
- scipy/special/xsf/cephes/jv.h +0 -715
- scipy/special/xsf/cephes/k0.h +0 -164
- scipy/special/xsf/cephes/k1.h +0 -163
- scipy/special/xsf/cephes/kn.h +0 -243
- scipy/special/xsf/cephes/lanczos.h +0 -112
- scipy/special/xsf/cephes/ndtr.h +0 -275
- scipy/special/xsf/cephes/poch.h +0 -85
- scipy/special/xsf/cephes/polevl.h +0 -167
- scipy/special/xsf/cephes/psi.h +0 -194
- scipy/special/xsf/cephes/rgamma.h +0 -111
- scipy/special/xsf/cephes/scipy_iv.h +0 -811
- scipy/special/xsf/cephes/shichi.h +0 -248
- scipy/special/xsf/cephes/sici.h +0 -224
- scipy/special/xsf/cephes/sindg.h +0 -221
- scipy/special/xsf/cephes/tandg.h +0 -139
- scipy/special/xsf/cephes/trig.h +0 -58
- scipy/special/xsf/cephes/unity.h +0 -186
- scipy/special/xsf/cephes/zeta.h +0 -172
- scipy/special/xsf/config.h +0 -304
- scipy/special/xsf/digamma.h +0 -205
- scipy/special/xsf/error.h +0 -57
- scipy/special/xsf/evalpoly.h +0 -47
- scipy/special/xsf/expint.h +0 -266
- scipy/special/xsf/hyp2f1.h +0 -694
- scipy/special/xsf/iv_ratio.h +0 -173
- scipy/special/xsf/lambertw.h +0 -150
- scipy/special/xsf/loggamma.h +0 -163
- scipy/special/xsf/sici.h +0 -200
- scipy/special/xsf/tools.h +0 -427
- scipy/special/xsf/trig.h +0 -164
- scipy/special/xsf/wright_bessel.h +0 -843
- scipy/special/xsf/zlog1.h +0 -35
- scipy/stats/_mvn.cpython-313-aarch64-linux-musl.so +0 -0
- scipy.libs/libgcc_s-7393e603.so.1 +0 -0
- scipy.libs/libgfortran-eb933d8e.so.5.0.0 +0 -0
- {scipy-1.15.3.dist-info → scipy-1.16.0rc2.dist-info}/WHEEL +0 -0
@@ -1,15 +1,18 @@
|
|
1
1
|
import functools
|
2
2
|
from abc import ABC, abstractmethod
|
3
3
|
from functools import cached_property
|
4
|
+
import inspect
|
4
5
|
import math
|
5
6
|
|
6
7
|
import numpy as np
|
7
8
|
from numpy import inf
|
8
9
|
|
9
|
-
from scipy._lib.
|
10
|
+
from scipy._lib._array_api import xp_promote
|
11
|
+
from scipy._lib._util import _rng_spawn, _RichResult
|
10
12
|
from scipy._lib._docscrape import ClassDoc, NumpyDocString
|
11
13
|
from scipy import special, stats
|
12
|
-
from scipy.
|
14
|
+
from scipy.special._ufuncs import _log1mexp
|
15
|
+
from scipy.integrate import tanhsinh as _tanhsinh, nsum
|
13
16
|
from scipy.optimize._bracket import _bracket_root, _bracket_minimum
|
14
17
|
from scipy.optimize._chandrupatla import _chandrupatla, _chandrupatla_minimize
|
15
18
|
from scipy.stats._probability_distribution import _ProbabilityDistribution
|
@@ -207,7 +210,7 @@ class _Domain(ABC):
|
|
207
210
|
or not (False). Used for input validation.
|
208
211
|
get_numerical_endpoints()
|
209
212
|
Gets the numerical values of the domain endpoints, which may have been
|
210
|
-
defined symbolically.
|
213
|
+
defined symbolically or through a callable.
|
211
214
|
__str__()
|
212
215
|
Returns a text representation of the domain (e.g. ``[0, b)``).
|
213
216
|
Used for generating documentation.
|
@@ -232,8 +235,8 @@ class _Domain(ABC):
|
|
232
235
|
raise NotImplementedError()
|
233
236
|
|
234
237
|
|
235
|
-
class
|
236
|
-
r""" Representation of
|
238
|
+
class _Interval(_Domain):
|
239
|
+
r""" Representation of an interval defined by two endpoints.
|
237
240
|
|
238
241
|
Each endpoint may be a finite scalar, positive or negative infinity, or
|
239
242
|
be given by a single parameter. The domain may include the endpoints or
|
@@ -247,10 +250,12 @@ class _SimpleDomain(_Domain):
|
|
247
250
|
----------
|
248
251
|
symbols : dict
|
249
252
|
Inherited. A map from special values to symbols for use in `__str__`.
|
250
|
-
endpoints : 2-tuple of float(s) and/or str(s)
|
253
|
+
endpoints : 2-tuple of float(s) and/or str(s) and/or callable(s).
|
251
254
|
A tuple with two values. Each may be either a float (the numerical
|
252
|
-
value of the endpoints of the domain)
|
253
|
-
parameters that will define the endpoint)
|
255
|
+
value of the endpoints of the domain), a string (the name of the
|
256
|
+
parameters that will define the endpoint), or a callable taking the
|
257
|
+
parameters used to define the endpoints of the domain as keyword only
|
258
|
+
arguments and returning a numerical value for the endpoint.
|
254
259
|
inclusive : 2-tuple of bools
|
255
260
|
A tuple with two boolean values; each indicates whether the
|
256
261
|
corresponding endpoint is included within the domain or not.
|
@@ -261,9 +266,11 @@ class _SimpleDomain(_Domain):
|
|
261
266
|
Records any parameters used to define the endpoints of the domain
|
262
267
|
get_numerical_endpoints(parameter_values)
|
263
268
|
Gets the numerical values of the domain endpoints, which may have been
|
264
|
-
defined symbolically.
|
269
|
+
defined symbolically or through a callable.
|
265
270
|
contains(item, parameter_values)
|
266
271
|
Determines whether the argument is contained within the domain
|
272
|
+
draw(size, rng, proportions, parameter_values)
|
273
|
+
Draws random values based on the domain.
|
267
274
|
|
268
275
|
"""
|
269
276
|
def __init__(self, endpoints=(-inf, inf), inclusive=(False, False)):
|
@@ -298,8 +305,9 @@ class _SimpleDomain(_Domain):
|
|
298
305
|
def get_numerical_endpoints(self, parameter_values):
|
299
306
|
r""" Get the numerical values of the domain endpoints.
|
300
307
|
|
301
|
-
Domain endpoints may be defined symbolically
|
302
|
-
values of the endpoints given numerical values for
|
308
|
+
Domain endpoints may be defined symbolically or through a callable.
|
309
|
+
This returns numerical values of the endpoints given numerical values for
|
310
|
+
any variables.
|
303
311
|
|
304
312
|
Parameters
|
305
313
|
----------
|
@@ -313,15 +321,22 @@ class _SimpleDomain(_Domain):
|
|
313
321
|
Numerical values of the endpoints
|
314
322
|
|
315
323
|
"""
|
316
|
-
# TODO: ensure outputs are floats
|
317
324
|
a, b = self.endpoints
|
318
325
|
# If `a` (`b`) is a string - the name of the parameter that defines
|
319
326
|
# the endpoint of the domain - then corresponding numerical values
|
320
|
-
# will be found in the `parameter_values` dictionary.
|
321
|
-
#
|
327
|
+
# will be found in the `parameter_values` dictionary.
|
328
|
+
# If a callable, it will be executed with `parameter_values` passed as
|
329
|
+
# keyword arguments, and it will return the numerical values.
|
330
|
+
# Otherwise, it is itself the array of numerical values of the endpoint.
|
322
331
|
try:
|
323
|
-
|
324
|
-
|
332
|
+
if callable(a):
|
333
|
+
a = a(**parameter_values)
|
334
|
+
else:
|
335
|
+
a = np.asarray(parameter_values.get(a, a))
|
336
|
+
if callable(b):
|
337
|
+
b = b(**parameter_values)
|
338
|
+
else:
|
339
|
+
b = np.asarray(parameter_values.get(b, b))
|
325
340
|
except TypeError as e:
|
326
341
|
message = ("The endpoints of the distribution are defined by "
|
327
342
|
"parameters, but their values were not provided. When "
|
@@ -329,7 +344,9 @@ class _SimpleDomain(_Domain):
|
|
329
344
|
"all required distribution parameters as keyword "
|
330
345
|
"arguments.")
|
331
346
|
raise TypeError(message) from e
|
332
|
-
|
347
|
+
# Floating point types are used for even integer parameters.
|
348
|
+
# Convert to float here to ensure consistency throughout framework.
|
349
|
+
a, b = xp_promote(a, b, force_floating=True, xp=np)
|
333
350
|
return a, b
|
334
351
|
|
335
352
|
def contains(self, item, parameter_values=None):
|
@@ -366,44 +383,6 @@ class _SimpleDomain(_Domain):
|
|
366
383
|
in_right = item <= b if right_inclusive else item < b
|
367
384
|
return in_left & in_right
|
368
385
|
|
369
|
-
|
370
|
-
class _RealDomain(_SimpleDomain):
|
371
|
-
r""" Represents a simply-connected subset of the real line; i.e., an interval
|
372
|
-
|
373
|
-
Completes the implementation of the `_SimpleDomain` class for simple
|
374
|
-
domains on the real line.
|
375
|
-
|
376
|
-
Methods
|
377
|
-
-------
|
378
|
-
define_parameters(*parameters)
|
379
|
-
(Inherited) Records any parameters used to define the endpoints of the
|
380
|
-
domain.
|
381
|
-
get_numerical_endpoints(parameter_values)
|
382
|
-
(Inherited) Gets the numerical values of the domain endpoints, which
|
383
|
-
may have been defined symbolically.
|
384
|
-
contains(item, parameter_values)
|
385
|
-
(Inherited) Determines whether the argument is contained within the
|
386
|
-
domain
|
387
|
-
__str__()
|
388
|
-
Returns a string representation of the domain, e.g. "[a, b)".
|
389
|
-
draw(size, rng, proportions, parameter_values)
|
390
|
-
Draws random values based on the domain. Proportions of values within
|
391
|
-
the domain, on the endpoints of the domain, outside the domain,
|
392
|
-
and having value NaN are specified by `proportions`.
|
393
|
-
|
394
|
-
"""
|
395
|
-
|
396
|
-
def __str__(self):
|
397
|
-
a, b = self.endpoints
|
398
|
-
left_inclusive, right_inclusive = self.inclusive
|
399
|
-
|
400
|
-
left = "[" if left_inclusive else "("
|
401
|
-
a = self.symbols.get(a, f"{a}")
|
402
|
-
right = "]" if right_inclusive else ")"
|
403
|
-
b = self.symbols.get(b, f"{b}")
|
404
|
-
|
405
|
-
return f"{left}{a}, {b}{right}"
|
406
|
-
|
407
386
|
def draw(self, n, type_, min, max, squeezed_base_shape, rng=None):
|
408
387
|
r""" Draw random values from the domain.
|
409
388
|
|
@@ -428,6 +407,9 @@ class _RealDomain(_SimpleDomain):
|
|
428
407
|
"""
|
429
408
|
rng = np.random.default_rng(rng)
|
430
409
|
|
410
|
+
def ints(*args, **kwargs): return rng.integers(*args, **kwargs, endpoint=True)
|
411
|
+
uniform = rng.uniform if isinstance(self, _RealInterval) else ints
|
412
|
+
|
431
413
|
# get copies of min and max with no nans so that uniform doesn't fail
|
432
414
|
min_nn, max_nn = min.copy(), max.copy()
|
433
415
|
i = np.isnan(min_nn) | np.isnan(max_nn)
|
@@ -437,7 +419,7 @@ class _RealDomain(_SimpleDomain):
|
|
437
419
|
shape = (n,) + squeezed_base_shape
|
438
420
|
|
439
421
|
if type_ == 'in':
|
440
|
-
z =
|
422
|
+
z = uniform(min_nn, max_nn, size=shape)
|
441
423
|
|
442
424
|
elif type_ == 'on':
|
443
425
|
z_on_shape = shape
|
@@ -447,9 +429,8 @@ class _RealDomain(_SimpleDomain):
|
|
447
429
|
z[~i] = max
|
448
430
|
|
449
431
|
elif type_ == 'out':
|
450
|
-
#
|
451
|
-
|
452
|
-
zr = max_nn + rng.uniform(size=shape)
|
432
|
+
z = min_nn - uniform(1, 5, size=shape) # 1, 5 is arbitary; we just want
|
433
|
+
zr = max_nn + uniform(1, 5, size=shape) # some numbers outside domain
|
453
434
|
i = rng.random(size=n) < 0.5
|
454
435
|
z[i] = zr[i]
|
455
436
|
|
@@ -459,16 +440,114 @@ class _RealDomain(_SimpleDomain):
|
|
459
440
|
return z
|
460
441
|
|
461
442
|
|
462
|
-
class
|
463
|
-
r"""
|
443
|
+
class _RealInterval(_Interval):
|
444
|
+
r""" Represents a simply-connected subset of the real line; i.e., an interval
|
445
|
+
|
446
|
+
Completes the implementation of the `_Interval` class for intervals
|
447
|
+
on the real line.
|
448
|
+
|
449
|
+
Methods
|
450
|
+
-------
|
451
|
+
define_parameters(*parameters)
|
452
|
+
(Inherited) Records any parameters used to define the endpoints of the
|
453
|
+
domain.
|
454
|
+
get_numerical_endpoints(parameter_values)
|
455
|
+
(Inherited) Gets the numerical values of the domain endpoints, which
|
456
|
+
may have been defined symbolically.
|
457
|
+
contains(item, parameter_values)
|
458
|
+
(Inherited) Determines whether the argument is contained within the
|
459
|
+
domain
|
460
|
+
__str__()
|
461
|
+
Returns a string representation of the domain, e.g. "[a, b)".
|
462
|
+
"""
|
463
|
+
|
464
|
+
def __str__(self):
|
465
|
+
a, b = self.endpoints
|
466
|
+
a, b = self._get_endpoint_str(a, "f1"), self._get_endpoint_str(b, "f2")
|
467
|
+
left_inclusive, right_inclusive = self.inclusive
|
468
|
+
left = "[" if left_inclusive else "("
|
469
|
+
right = "]" if right_inclusive else ")"
|
470
|
+
|
471
|
+
return f"{left}{a}, {b}{right}"
|
472
|
+
|
473
|
+
def _get_endpoint_str(self, endpoint, funcname):
|
474
|
+
if callable(endpoint):
|
475
|
+
if endpoint.__doc__ is not None:
|
476
|
+
return endpoint.__doc__
|
477
|
+
params = inspect.signature(endpoint).parameters.values()
|
478
|
+
params = [
|
479
|
+
p.name for p in params if p.kind == inspect.Parameter.KEYWORD_ONLY
|
480
|
+
]
|
481
|
+
return f"{funcname}({','.join(params)})"
|
482
|
+
return self.symbols.get(endpoint, f"{endpoint}")
|
483
|
+
|
484
|
+
|
485
|
+
class _IntegerInterval(_Interval):
|
486
|
+
r""" Represents an interval of integers
|
464
487
|
|
465
|
-
Completes the implementation of the `
|
466
|
-
|
488
|
+
Completes the implementation of the `_Interval` class for simple
|
489
|
+
domains on the integers.
|
490
|
+
|
491
|
+
Methods
|
492
|
+
-------
|
493
|
+
define_parameters(*parameters)
|
494
|
+
(Inherited) Records any parameters used to define the endpoints of the
|
495
|
+
domain.
|
496
|
+
get_numerical_endpoints(parameter_values)
|
497
|
+
(Inherited) Gets the numerical values of the domain endpoints, which
|
498
|
+
may have been defined symbolically.
|
499
|
+
contains(item, parameter_values)
|
500
|
+
(Overridden) Determines whether the argument is contained within the
|
501
|
+
domain
|
502
|
+
draw(n, type_, min, max, squeezed_base_shape, rng=None)
|
503
|
+
(Inherited) Draws random values based on the domain.
|
504
|
+
__str__()
|
505
|
+
Returns a string representation of the domain, e.g. "{a, a+1, ..., b-1, b}".
|
467
506
|
|
468
|
-
To be completed when needed.
|
469
507
|
"""
|
470
|
-
def
|
471
|
-
|
508
|
+
def contains(self, item, parameter_values=None):
|
509
|
+
super_contains = super().contains(item, parameter_values)
|
510
|
+
integral = (item == np.round(item))
|
511
|
+
return super_contains & integral
|
512
|
+
|
513
|
+
def __str__(self):
|
514
|
+
a, b = self.endpoints
|
515
|
+
a = self.symbols.get(a, a)
|
516
|
+
b = self.symbols.get(b, b)
|
517
|
+
|
518
|
+
a_str, b_str = isinstance(a, str), isinstance(b, str)
|
519
|
+
a_inf = a == r"-\infty" if a_str else np.isinf(a)
|
520
|
+
b_inf = b == r"\infty" if b_str else np.isinf(b)
|
521
|
+
|
522
|
+
# This doesn't work well for cases where ``a`` is floating point
|
523
|
+
# number large enough that ``nextafter(a, inf) > a + 1``, and
|
524
|
+
# similarly for ``b`` and nextafter(b, -inf). There may not be any
|
525
|
+
# distributions fit for SciPy where we would actually need to handle these
|
526
|
+
# cases though.
|
527
|
+
ap1 = f"{a} + 1" if a_str else f"{a + 1}"
|
528
|
+
bm1 = f"{b} - 1" if b_str else f"{b - 1}"
|
529
|
+
|
530
|
+
if not a_str and not b_str:
|
531
|
+
gap = b - a
|
532
|
+
if gap == 3:
|
533
|
+
return f"\\{{{a}, {ap1}, {bm1}, {b}\\}}"
|
534
|
+
if gap == 2:
|
535
|
+
return f"\\{{{a}, {ap1}, {b}\\}}"
|
536
|
+
if gap == 1:
|
537
|
+
return f"\\{{{a}, {b}\\}}"
|
538
|
+
if gap == 0:
|
539
|
+
return f"\\{{{a}\\}}"
|
540
|
+
|
541
|
+
if not a_inf and b_inf:
|
542
|
+
ap2 = f"{a} + 2" if a_str else f"{a + 2}"
|
543
|
+
return f"\\{{{a}, {ap1}, {ap2}, ...\\}}"
|
544
|
+
if a_inf and not b_inf:
|
545
|
+
bm2 = f"{b} - 2" if b_str else f"{b - 2}"
|
546
|
+
return f"\\{{{b}, {bm1}, {bm2}, ...\\}}"
|
547
|
+
if a_inf and b_inf:
|
548
|
+
return "\\{..., -2, -1, 0, 1, 2, ...\\}"
|
549
|
+
|
550
|
+
return f"\\{{{a}, {ap1}, ..., {bm1}, {b}\\}}"
|
472
551
|
|
473
552
|
|
474
553
|
class _Parameter(ABC):
|
@@ -516,7 +595,7 @@ class _Parameter(ABC):
|
|
516
595
|
self.symbol = symbol or name
|
517
596
|
self.domain = domain
|
518
597
|
if typical is not None and not isinstance(typical, _Domain):
|
519
|
-
typical =
|
598
|
+
typical = domain.__class__(typical)
|
520
599
|
self.typical = typical or domain
|
521
600
|
|
522
601
|
def __str__(self):
|
@@ -818,7 +897,7 @@ class _Parameterization:
|
|
818
897
|
# we can draw values in order a, b, c.
|
819
898
|
parameter_values = {}
|
820
899
|
|
821
|
-
if not len(sizes) or not np.iterable(sizes[0]):
|
900
|
+
if sizes is None or not len(sizes) or not np.iterable(sizes[0]):
|
822
901
|
sizes = [sizes]*len(self.parameters)
|
823
902
|
|
824
903
|
for size, param in zip(sizes, self.parameters.values()):
|
@@ -835,6 +914,7 @@ def _set_invalid_nan(f):
|
|
835
914
|
# Wrapper for input / output validation and standardization of distribution
|
836
915
|
# functions that accept either the quantile or percentile as an argument:
|
837
916
|
# logpdf, pdf
|
917
|
+
# logpmf, pmf
|
838
918
|
# logcdf, cdf
|
839
919
|
# logccdf, ccdf
|
840
920
|
# ilogcdf, icdf
|
@@ -850,12 +930,15 @@ def _set_invalid_nan(f):
|
|
850
930
|
endpoints = {'icdf': (0, 1), 'iccdf': (0, 1),
|
851
931
|
'ilogcdf': (-np.inf, 0), 'ilogccdf': (-np.inf, 0)}
|
852
932
|
replacements = {'logpdf': (-inf, -inf), 'pdf': (0, 0),
|
933
|
+
'logpmf': (-inf, -inf), 'pmf': (0, 0),
|
853
934
|
'_logcdf1': (-inf, 0), '_logccdf1': (0, -inf),
|
854
935
|
'_cdf1': (0, 1), '_ccdf1': (1, 0)}
|
855
|
-
replace_strict = {'pdf', 'logpdf'}
|
936
|
+
replace_strict = {'pdf', 'logpdf', 'pmf', 'logpmf'}
|
856
937
|
replace_exact = {'icdf', 'iccdf', 'ilogcdf', 'ilogccdf'}
|
857
938
|
clip = {'_cdf1', '_ccdf1'}
|
858
939
|
clip_log = {'_logcdf1', '_logccdf1'}
|
940
|
+
# relevant to discrete distributions only
|
941
|
+
replace_non_integral = {'pmf', 'logpmf', 'pdf', 'logpdf'}
|
859
942
|
|
860
943
|
@functools.wraps(f)
|
861
944
|
def filtered(self, x, *args, **kwargs):
|
@@ -866,6 +949,9 @@ def _set_invalid_nan(f):
|
|
866
949
|
x = np.asarray(x)
|
867
950
|
dtype = self._dtype
|
868
951
|
shape = self._shape
|
952
|
+
discrete = isinstance(self, DiscreteDistribution)
|
953
|
+
keep_low_endpoint = discrete and method_name in {'_cdf1', '_logcdf1',
|
954
|
+
'_ccdf1', '_logccdf1'}
|
869
955
|
|
870
956
|
# Ensure that argument is at least as precise as distribution
|
871
957
|
# parameters, which are already at least floats. This will avoid issues
|
@@ -894,7 +980,7 @@ def _set_invalid_nan(f):
|
|
894
980
|
# and the result will be set to the appropriate value.
|
895
981
|
left_inc, right_inc = self._variable.domain.inclusive
|
896
982
|
mask_low = (x < low if (method_name in replace_strict and left_inc)
|
897
|
-
else x <= low)
|
983
|
+
or keep_low_endpoint else x <= low)
|
898
984
|
mask_high = (x > high if (method_name in replace_strict and right_inc)
|
899
985
|
else x >= high)
|
900
986
|
mask_invalid = (mask_low | mask_high)
|
@@ -911,6 +997,14 @@ def _set_invalid_nan(f):
|
|
911
997
|
any_endpoint = (mask_endpoint if mask_endpoint.shape == ()
|
912
998
|
else np.any(mask_endpoint))
|
913
999
|
|
1000
|
+
# Check for non-integral arguments to PMF method
|
1001
|
+
# or PDF of a discrete distribution.
|
1002
|
+
any_non_integral = False
|
1003
|
+
if discrete and method_name in replace_non_integral:
|
1004
|
+
mask_non_integral = (x != np.floor(x))
|
1005
|
+
any_non_integral = (mask_non_integral if mask_non_integral.shape == ()
|
1006
|
+
else np.any(mask_non_integral))
|
1007
|
+
|
914
1008
|
# Set out-of-domain arguments to NaN. The result will be set to the
|
915
1009
|
# appropriate value later.
|
916
1010
|
if any_invalid:
|
@@ -928,12 +1022,19 @@ def _set_invalid_nan(f):
|
|
928
1022
|
|
929
1023
|
if res.shape != shape: # faster to check first
|
930
1024
|
res = np.broadcast_to(res, self._shape)
|
931
|
-
res_needs_copy = res_needs_copy or any_invalid
|
1025
|
+
res_needs_copy = (res_needs_copy or any_invalid
|
1026
|
+
or any_endpoint or any_non_integral)
|
932
1027
|
|
933
1028
|
if res_needs_copy:
|
934
1029
|
res = np.array(res, dtype=dtype, copy=True)
|
935
1030
|
|
936
|
-
#
|
1031
|
+
# For non-integral arguments to PMF (and PDF of discrete distribution)
|
1032
|
+
# replace with zero.
|
1033
|
+
if any_non_integral:
|
1034
|
+
zero = -np.inf if method_name in {'logpmf', 'logpdf'} else 0
|
1035
|
+
res[mask_non_integral & ~np.isnan(res)] = zero
|
1036
|
+
|
1037
|
+
# For arguments outside the function domain, replace results
|
937
1038
|
if any_invalid:
|
938
1039
|
replace_low, replace_high = (
|
939
1040
|
replacements.get(method_name, (np.nan, np.nan)))
|
@@ -954,7 +1055,8 @@ def _set_invalid_nan(f):
|
|
954
1055
|
a[mask_high_endpoint] if method_name.endswith('ccdf')
|
955
1056
|
else b[mask_high_endpoint])
|
956
1057
|
|
957
|
-
|
1058
|
+
if not keep_low_endpoint:
|
1059
|
+
res[mask_low_endpoint] = replace_low_endpoint
|
958
1060
|
res[mask_high_endpoint] = replace_high_endpoint
|
959
1061
|
|
960
1062
|
# Clip probabilities to [0, 1]
|
@@ -1147,52 +1249,11 @@ def _kwargs2args(f, args=None, kwargs=None):
|
|
1147
1249
|
def wrapped(x, *args):
|
1148
1250
|
return f(x, *args[:n_args], **dict(zip(names, args[n_args:])))
|
1149
1251
|
|
1150
|
-
args =
|
1252
|
+
args = tuple(args) + tuple(kwargs.values())
|
1151
1253
|
|
1152
1254
|
return wrapped, args
|
1153
1255
|
|
1154
1256
|
|
1155
|
-
def _log1mexp(x):
|
1156
|
-
r"""Compute the log of the complement of the exponential.
|
1157
|
-
|
1158
|
-
This function is equivalent to::
|
1159
|
-
|
1160
|
-
log1mexp(x) = np.log(1-np.exp(x))
|
1161
|
-
|
1162
|
-
but avoids loss of precision when ``np.exp(x)`` is nearly 0 or 1.
|
1163
|
-
|
1164
|
-
Parameters
|
1165
|
-
----------
|
1166
|
-
x : array_like
|
1167
|
-
Input array.
|
1168
|
-
|
1169
|
-
Returns
|
1170
|
-
-------
|
1171
|
-
y : ndarray
|
1172
|
-
An array of the same shape as `x`.
|
1173
|
-
|
1174
|
-
Examples
|
1175
|
-
--------
|
1176
|
-
>>> import numpy as np
|
1177
|
-
>>> from scipy.stats._distribution_infrastructure import _log1mexp
|
1178
|
-
>>> x = 1e-300 # log of a number very close to 1
|
1179
|
-
>>> _log1mexp(x) # log of the complement of a number very close to 1
|
1180
|
-
-690.7755278982137
|
1181
|
-
>>> # np.log1p(-np.exp(x)) # -inf; emits warning
|
1182
|
-
|
1183
|
-
"""
|
1184
|
-
def f1(x):
|
1185
|
-
# good for exp(x) close to 0
|
1186
|
-
return np.log1p(-np.exp(x))
|
1187
|
-
|
1188
|
-
def f2(x):
|
1189
|
-
# good for exp(x) close to 1
|
1190
|
-
with np.errstate(divide='ignore'):
|
1191
|
-
return np.real(np.log(-special.expm1(x + 0j)))
|
1192
|
-
|
1193
|
-
return _lazywhere(x < -1, (x,), f=f1, f2=f2)[()]
|
1194
|
-
|
1195
|
-
|
1196
1257
|
def _logexpxmexpy(x, y):
|
1197
1258
|
""" Compute the log of the difference of the exponentials of two arguments.
|
1198
1259
|
|
@@ -1260,7 +1321,7 @@ def _combine_docs(dist_family, *, include_examples=True):
|
|
1260
1321
|
fields.remove('Examples')
|
1261
1322
|
|
1262
1323
|
doc = ClassDoc(dist_family)
|
1263
|
-
superdoc = ClassDoc(
|
1324
|
+
superdoc = ClassDoc(UnivariateDistribution)
|
1264
1325
|
for field in fields:
|
1265
1326
|
if field in {"Methods", "Attributes"}:
|
1266
1327
|
doc[field] = superdoc[field]
|
@@ -1278,7 +1339,7 @@ def _combine_docs(dist_family, *, include_examples=True):
|
|
1278
1339
|
def _generate_domain_support(dist_family):
|
1279
1340
|
n_parameterizations = len(dist_family._parameterizations)
|
1280
1341
|
|
1281
|
-
domain = f"\nfor :math:`x
|
1342
|
+
domain = f"\nfor :math:`x \\in {dist_family._variable.domain}`.\n"
|
1282
1343
|
|
1283
1344
|
if n_parameterizations == 0:
|
1284
1345
|
support = """
|
@@ -1328,7 +1389,7 @@ def _generate_example(dist_family):
|
|
1328
1389
|
|
1329
1390
|
p = 0.32
|
1330
1391
|
x = round(X.icdf(p), 2)
|
1331
|
-
y = round(X.icdf(2 * p), 2)
|
1392
|
+
y = round(X.icdf(2 * p), 2) # noqa: F841
|
1332
1393
|
|
1333
1394
|
example = f"""
|
1334
1395
|
To use the distribution class, it must be instantiated using keyword
|
@@ -1363,19 +1424,24 @@ def _generate_example(dist_family):
|
|
1363
1424
|
"""
|
1364
1425
|
|
1365
1426
|
example += f"""
|
1366
|
-
To evaluate the probability density function of the underlying distribution
|
1427
|
+
To evaluate the probability density/mass function of the underlying distribution
|
1367
1428
|
at argument ``x={x}``:
|
1368
1429
|
|
1369
1430
|
>>> x = {x}
|
1370
|
-
>>> X.pdf(x)
|
1371
|
-
{X.pdf(x)}
|
1431
|
+
>>> X.pdf(x), X.pmf(x)
|
1432
|
+
{X.pdf(x), X.pmf(x)}
|
1372
1433
|
|
1373
1434
|
The cumulative distribution function, its complement, and the logarithm
|
1374
1435
|
of these functions are evaluated similarly.
|
1375
1436
|
|
1376
1437
|
>>> np.allclose(np.exp(X.logccdf(x)), 1 - X.cdf(x))
|
1377
1438
|
True
|
1439
|
+
"""
|
1378
1440
|
|
1441
|
+
# When two-arg CDF is implemented for DiscreteDistribution, consider removing
|
1442
|
+
# the special-casing here.
|
1443
|
+
if issubclass(dist_family, ContinuousDistribution):
|
1444
|
+
example_continuous = f"""
|
1379
1445
|
The inverse of these functions with respect to the argument ``x`` is also
|
1380
1446
|
available.
|
1381
1447
|
|
@@ -1391,22 +1457,40 @@ def _generate_example(dist_family):
|
|
1391
1457
|
>>> y = {y}
|
1392
1458
|
>>> np.allclose(X.ccdf(x, y), 1 - (X.cdf(y) - X.cdf(x)))
|
1393
1459
|
True
|
1460
|
+
"""
|
1461
|
+
example += example_continuous
|
1394
1462
|
|
1463
|
+
example += f"""
|
1395
1464
|
There are methods for computing measures of central tendency,
|
1396
1465
|
dispersion, higher moments, and entropy.
|
1397
1466
|
|
1398
1467
|
>>> X.mean(), X.median(), X.mode()
|
1399
1468
|
{X.mean(), X.median(), X.mode()}
|
1469
|
+
|
1400
1470
|
>>> X.variance(), X.standard_deviation()
|
1401
1471
|
{X.variance(), X.standard_deviation()}
|
1472
|
+
|
1402
1473
|
>>> X.skewness(), X.kurtosis()
|
1403
1474
|
{X.skewness(), X.kurtosis()}
|
1475
|
+
|
1404
1476
|
>>> np.allclose(X.moment(order=6, kind='standardized'),
|
1405
1477
|
... X.moment(order=6, kind='central') / X.variance()**3)
|
1406
1478
|
True
|
1479
|
+
"""
|
1480
|
+
|
1481
|
+
# When logentropy is implemented for DiscreteDistribution, remove special-casing
|
1482
|
+
if issubclass(dist_family, ContinuousDistribution):
|
1483
|
+
example += """
|
1407
1484
|
>>> np.allclose(np.exp(X.logentropy()), X.entropy())
|
1408
1485
|
True
|
1486
|
+
"""
|
1487
|
+
else:
|
1488
|
+
example += f"""
|
1489
|
+
>>> X.entropy()
|
1490
|
+
{X.entropy()}
|
1491
|
+
"""
|
1409
1492
|
|
1493
|
+
example += f"""
|
1410
1494
|
Pseudo-random samples can be drawn from
|
1411
1495
|
the underlying distribution using ``sample``.
|
1412
1496
|
|
@@ -1419,7 +1503,7 @@ def _generate_example(dist_family):
|
|
1419
1503
|
return example
|
1420
1504
|
|
1421
1505
|
|
1422
|
-
class
|
1506
|
+
class UnivariateDistribution(_ProbabilityDistribution):
|
1423
1507
|
r""" Class that represents a continuous statistical distribution.
|
1424
1508
|
|
1425
1509
|
Parameters
|
@@ -2004,12 +2088,24 @@ class ContinuousDistribution(_ProbabilityDistribution):
|
|
2004
2088
|
args = np.broadcast_arrays(*args)
|
2005
2089
|
# If we know the median or mean, consider breaking up the interval
|
2006
2090
|
rtol = None if _isnull(self.tol) else self.tol
|
2007
|
-
res = _tanhsinh(f, a, b, args=args, log=log, rtol=rtol)
|
2008
2091
|
# For now, we ignore the status, but I want to return the error
|
2009
2092
|
# estimate - see question 5 at the top.
|
2010
|
-
|
2011
|
-
|
2012
|
-
|
2093
|
+
if isinstance(self, ContinuousDistribution):
|
2094
|
+
res = _tanhsinh(f, a, b, args=args, log=log, rtol=rtol)
|
2095
|
+
return res.integral
|
2096
|
+
else:
|
2097
|
+
res = nsum(f, a, b, args=args, log=log, tolerances=dict(rtol=rtol)).sum
|
2098
|
+
res = np.asarray(res)
|
2099
|
+
# The result should be nan when parameters are nan, so need to special
|
2100
|
+
# case this.
|
2101
|
+
cond = np.isnan(params.popitem()[1]) if params else np.True_
|
2102
|
+
cond = np.broadcast_to(cond, a.shape)
|
2103
|
+
res[(a > b)] = -np.inf if log else 0 # fix in nsum?
|
2104
|
+
res[cond] = np.nan
|
2105
|
+
|
2106
|
+
return res[()]
|
2107
|
+
|
2108
|
+
def _solve_bounded(self, f, p, *, bounds=None, params=None, xatol=None):
|
2013
2109
|
# Finds the argument of a function that produces the desired output.
|
2014
2110
|
# Much of this should be added to _bracket_root / _chandrupatla.
|
2015
2111
|
xmin, xmax = self._support(**params) if bounds is None else bounds
|
@@ -2018,7 +2114,10 @@ class ContinuousDistribution(_ProbabilityDistribution):
|
|
2018
2114
|
p, xmin, xmax = np.broadcast_arrays(p, xmin, xmax)
|
2019
2115
|
if not p.size:
|
2020
2116
|
# might need to figure out result type based on p
|
2021
|
-
|
2117
|
+
res = _RichResult()
|
2118
|
+
empty = np.empty(p.shape, dtype=self._dtype)
|
2119
|
+
res.xl, res.x, res.xr = empty, empty, empty
|
2120
|
+
res.fl, res.fr = empty, empty
|
2022
2121
|
|
2023
2122
|
def f2(x, _p, **kwargs): # named `_p` to avoid conflict with shape `p`
|
2024
2123
|
return f(x, **kwargs) - _p
|
@@ -2039,8 +2138,11 @@ class ContinuousDistribution(_ProbabilityDistribution):
|
|
2039
2138
|
res = _bracket_root(f3, xl0=xl0, xr0=xr0, xmin=xmin, xmax=xmax, args=args)
|
2040
2139
|
# For now, we ignore the status, but I want to use the bracket width
|
2041
2140
|
# as an error estimate - see question 5 at the top.
|
2141
|
+
|
2042
2142
|
xrtol = None if _isnull(self.tol) else self.tol
|
2043
|
-
|
2143
|
+
xatol = None if xatol is None else xatol
|
2144
|
+
tolerances = dict(xrtol=xrtol, xatol=xatol, fatol=0, frtol=0)
|
2145
|
+
return _chandrupatla(f3, a=res.xl, b=res.xr, args=args, **tolerances)
|
2044
2146
|
|
2045
2147
|
## Other
|
2046
2148
|
|
@@ -2058,7 +2160,7 @@ class ContinuousDistribution(_ProbabilityDistribution):
|
|
2058
2160
|
# For more complete discussion of the considerations, see:
|
2059
2161
|
# https://github.com/scipy/scipy/pull/21050#discussion_r1707798901
|
2060
2162
|
method = getattr(self.__class__, method_name, None)
|
2061
|
-
super_method = getattr(
|
2163
|
+
super_method = getattr(UnivariateDistribution, method_name, None)
|
2062
2164
|
return method is not super_method
|
2063
2165
|
|
2064
2166
|
### Distribution properties
|
@@ -2187,8 +2289,8 @@ class ContinuousDistribution(_ProbabilityDistribution):
|
|
2187
2289
|
|
2188
2290
|
def _logentropy_quadrature(self, **params):
|
2189
2291
|
def logintegrand(x, **params):
|
2190
|
-
|
2191
|
-
return
|
2292
|
+
logpxf = self._logpxf_dispatch(x, **params)
|
2293
|
+
return logpxf + np.log(0j+logpxf)
|
2192
2294
|
res = self._quadrature(logintegrand, params=params, log=True)
|
2193
2295
|
return _log_real_standardize(res + np.pi*1j)
|
2194
2296
|
|
@@ -2214,9 +2316,12 @@ class ContinuousDistribution(_ProbabilityDistribution):
|
|
2214
2316
|
|
2215
2317
|
def _entropy_quadrature(self, **params):
|
2216
2318
|
def integrand(x, **params):
|
2217
|
-
|
2218
|
-
|
2219
|
-
|
2319
|
+
pxf = self._pxf_dispatch(x, **params)
|
2320
|
+
logpxf = self._logpxf_dispatch(x, **params)
|
2321
|
+
temp = np.asarray(pxf)
|
2322
|
+
i = (pxf != 0) # 0 * inf -> nan; should be 0
|
2323
|
+
temp[i] = pxf[i]*logpxf[i]
|
2324
|
+
return temp
|
2220
2325
|
return -self._quadrature(integrand, params=params)
|
2221
2326
|
|
2222
2327
|
@_set_invalid_nan_property
|
@@ -2254,17 +2359,18 @@ class ContinuousDistribution(_ProbabilityDistribution):
|
|
2254
2359
|
def _mode_formula(self, **params):
|
2255
2360
|
raise NotImplementedError(self._not_implemented)
|
2256
2361
|
|
2257
|
-
def _mode_optimization(self, **params):
|
2362
|
+
def _mode_optimization(self, xatol=None, **params):
|
2258
2363
|
if not self._size:
|
2259
2364
|
return np.empty(self._shape, dtype=self._dtype)
|
2260
2365
|
|
2261
2366
|
a, b = self._support(**params)
|
2262
2367
|
m = self._median_dispatch(**params)
|
2263
2368
|
|
2264
|
-
f, args = _kwargs2args(lambda x, **params: -self.
|
2369
|
+
f, args = _kwargs2args(lambda x, **params: -self._pxf_dispatch(x, **params),
|
2265
2370
|
args=(), kwargs=params)
|
2266
2371
|
res_b = _bracket_minimum(f, m, xmin=a, xmax=b, args=args)
|
2267
|
-
res = _chandrupatla_minimize(f, res_b.xl, res_b.xm, res_b.xr,
|
2372
|
+
res = _chandrupatla_minimize(f, res_b.xl, res_b.xm, res_b.xr,
|
2373
|
+
args=args, xatol=xatol)
|
2268
2374
|
mode = np.asarray(res.x)
|
2269
2375
|
mode_at_boundary = res_b.status == -1
|
2270
2376
|
mode_at_left = mode_at_boundary & (res_b.fl <= res_b.fm)
|
@@ -2328,7 +2434,7 @@ class ContinuousDistribution(_ProbabilityDistribution):
|
|
2328
2434
|
# See the note corresponding with the "Distribution Parameters" for more
|
2329
2435
|
# information.
|
2330
2436
|
|
2331
|
-
## Probability Density Functions
|
2437
|
+
## Probability Density/Mass Functions
|
2332
2438
|
|
2333
2439
|
@_set_invalid_nan
|
2334
2440
|
def logpdf(self, x, /, *, method=None):
|
@@ -2366,6 +2472,43 @@ class ContinuousDistribution(_ProbabilityDistribution):
|
|
2366
2472
|
def _pdf_logexp(self, x, **params):
|
2367
2473
|
return np.exp(self._logpdf_dispatch(x, **params))
|
2368
2474
|
|
2475
|
+
@_set_invalid_nan
|
2476
|
+
def logpmf(self, x, /, *, method=None):
|
2477
|
+
return self._logpmf_dispatch(x, method=method, **self._parameters)
|
2478
|
+
|
2479
|
+
@_dispatch
|
2480
|
+
def _logpmf_dispatch(self, x, *, method=None, **params):
|
2481
|
+
if self._overrides('_logpmf_formula'):
|
2482
|
+
method = self._logpmf_formula
|
2483
|
+
elif _isnull(self.tol): # ensure that developers override _logpmf
|
2484
|
+
method = self._logpmf_logexp
|
2485
|
+
return method
|
2486
|
+
|
2487
|
+
def _logpmf_formula(self, x, **params):
|
2488
|
+
raise NotImplementedError(self._not_implemented)
|
2489
|
+
|
2490
|
+
def _logpmf_logexp(self, x, **params):
|
2491
|
+
with np.errstate(divide='ignore'):
|
2492
|
+
return np.log(self._pmf_dispatch(x, **params))
|
2493
|
+
|
2494
|
+
@_set_invalid_nan
|
2495
|
+
def pmf(self, x, /, *, method=None):
|
2496
|
+
return self._pmf_dispatch(x, method=method, **self._parameters)
|
2497
|
+
|
2498
|
+
@_dispatch
|
2499
|
+
def _pmf_dispatch(self, x, *, method=None, **params):
|
2500
|
+
if self._overrides('_pmf_formula'):
|
2501
|
+
method = self._pmf_formula
|
2502
|
+
else:
|
2503
|
+
method = self._pmf_logexp
|
2504
|
+
return method
|
2505
|
+
|
2506
|
+
def _pmf_formula(self, x, **params):
|
2507
|
+
raise NotImplementedError(self._not_implemented)
|
2508
|
+
|
2509
|
+
def _pmf_logexp(self, x, **params):
|
2510
|
+
return np.exp(self._logpmf_dispatch(x, **params))
|
2511
|
+
|
2369
2512
|
## Cumulative Distribution Functions
|
2370
2513
|
|
2371
2514
|
def logcdf(self, x, y=None, /, *, method=None):
|
@@ -2431,7 +2574,7 @@ class ContinuousDistribution(_ProbabilityDistribution):
|
|
2431
2574
|
return out[()]
|
2432
2575
|
|
2433
2576
|
def _logcdf2_quadrature(self, x, y, **params):
|
2434
|
-
logres = self._quadrature(self.
|
2577
|
+
logres = self._quadrature(self._logpxf_dispatch, limits=(x, y),
|
2435
2578
|
log=True, params=params)
|
2436
2579
|
return logres
|
2437
2580
|
|
@@ -2472,7 +2615,7 @@ class ContinuousDistribution(_ProbabilityDistribution):
|
|
2472
2615
|
|
2473
2616
|
def _logcdf_quadrature(self, x, **params):
|
2474
2617
|
a, _ = self._support(**params)
|
2475
|
-
return self._quadrature(self.
|
2618
|
+
return self._quadrature(self._logpxf_dispatch, limits=(a, x),
|
2476
2619
|
params=params, log=True)
|
2477
2620
|
|
2478
2621
|
def cdf(self, x, y=None, /, *, method=None):
|
@@ -2536,11 +2679,11 @@ class ContinuousDistribution(_ProbabilityDistribution):
|
|
2536
2679
|
params_mask = {key: np.broadcast_to(val, mask.shape)[mask]
|
2537
2680
|
for key, val in params.items()}
|
2538
2681
|
out = np.asarray(out)
|
2539
|
-
out[mask] = self._cdf2_quadrature(x[mask], y[mask],
|
2682
|
+
out[mask] = self._cdf2_quadrature(x[mask], y[mask], **params_mask)
|
2540
2683
|
return out[()]
|
2541
2684
|
|
2542
2685
|
def _cdf2_quadrature(self, x, y, **params):
|
2543
|
-
return self._quadrature(self.
|
2686
|
+
return self._quadrature(self._pxf_dispatch, limits=(x, y), params=params)
|
2544
2687
|
|
2545
2688
|
@_set_invalid_nan
|
2546
2689
|
def _cdf1(self, x, *, method):
|
@@ -2582,7 +2725,7 @@ class ContinuousDistribution(_ProbabilityDistribution):
|
|
2582
2725
|
|
2583
2726
|
def _cdf_quadrature(self, x, **params):
|
2584
2727
|
a, _ = self._support(**params)
|
2585
|
-
return self._quadrature(self.
|
2728
|
+
return self._quadrature(self._pxf_dispatch, limits=(a, x),
|
2586
2729
|
params=params)
|
2587
2730
|
|
2588
2731
|
def logccdf(self, x, y=None, /, *, method=None):
|
@@ -2650,7 +2793,7 @@ class ContinuousDistribution(_ProbabilityDistribution):
|
|
2650
2793
|
|
2651
2794
|
def _logccdf_quadrature(self, x, **params):
|
2652
2795
|
_, b = self._support(**params)
|
2653
|
-
return self._quadrature(self.
|
2796
|
+
return self._quadrature(self._logpxf_dispatch, limits=(x, b),
|
2654
2797
|
params=params, log=True)
|
2655
2798
|
|
2656
2799
|
def ccdf(self, x, y=None, /, *, method=None):
|
@@ -2720,7 +2863,7 @@ class ContinuousDistribution(_ProbabilityDistribution):
|
|
2720
2863
|
|
2721
2864
|
def _ccdf_quadrature(self, x, **params):
|
2722
2865
|
_, b = self._support(**params)
|
2723
|
-
return self._quadrature(self.
|
2866
|
+
return self._quadrature(self._pxf_dispatch, limits=(x, b),
|
2724
2867
|
params=params)
|
2725
2868
|
|
2726
2869
|
## Inverse cumulative distribution functions
|
@@ -2746,7 +2889,7 @@ class ContinuousDistribution(_ProbabilityDistribution):
|
|
2746
2889
|
return self._ilogccdf_dispatch(_log1mexp(x), **params)
|
2747
2890
|
|
2748
2891
|
def _ilogcdf_inversion(self, x, **params):
|
2749
|
-
return self.
|
2892
|
+
return self._solve_bounded_continuous(self._logcdf_dispatch, x, params=params)
|
2750
2893
|
|
2751
2894
|
@_set_invalid_nan
|
2752
2895
|
def icdf(self, p, /, *, method=None):
|
@@ -2781,7 +2924,7 @@ class ContinuousDistribution(_ProbabilityDistribution):
|
|
2781
2924
|
return out[()]
|
2782
2925
|
|
2783
2926
|
def _icdf_inversion(self, x, **params):
|
2784
|
-
return self.
|
2927
|
+
return self._solve_bounded_continuous(self._cdf_dispatch, x, params=params)
|
2785
2928
|
|
2786
2929
|
@_set_invalid_nan
|
2787
2930
|
def ilogccdf(self, logp, /, *, method=None):
|
@@ -2804,7 +2947,7 @@ class ContinuousDistribution(_ProbabilityDistribution):
|
|
2804
2947
|
return self._ilogcdf_dispatch(_log1mexp(x), **params)
|
2805
2948
|
|
2806
2949
|
def _ilogccdf_inversion(self, x, **params):
|
2807
|
-
return self.
|
2950
|
+
return self._solve_bounded_continuous(self._logccdf_dispatch, x, params=params)
|
2808
2951
|
|
2809
2952
|
@_set_invalid_nan
|
2810
2953
|
def iccdf(self, p, /, *, method=None):
|
@@ -2839,7 +2982,7 @@ class ContinuousDistribution(_ProbabilityDistribution):
|
|
2839
2982
|
return out[()]
|
2840
2983
|
|
2841
2984
|
def _iccdf_inversion(self, x, **params):
|
2842
|
-
return self.
|
2985
|
+
return self._solve_bounded_continuous(self._ccdf_dispatch, x, params=params)
|
2843
2986
|
|
2844
2987
|
### Sampling Functions
|
2845
2988
|
# The following functions for drawing samples from the distribution are
|
@@ -2877,13 +3020,13 @@ class ContinuousDistribution(_ProbabilityDistribution):
|
|
2877
3020
|
sample_shape = (shape,) if not np.iterable(shape) else tuple(shape)
|
2878
3021
|
full_shape = sample_shape + self._shape
|
2879
3022
|
rng = np.random.default_rng(rng) if not isinstance(rng, qmc.QMCEngine) else rng
|
2880
|
-
res = self._sample_dispatch(
|
2881
|
-
|
3023
|
+
res = self._sample_dispatch(full_shape, method=method, rng=rng,
|
3024
|
+
**self._parameters)
|
2882
3025
|
|
2883
3026
|
return res.astype(self._dtype, copy=False)
|
2884
3027
|
|
2885
3028
|
@_dispatch
|
2886
|
-
def _sample_dispatch(self,
|
3029
|
+
def _sample_dispatch(self, full_shape, *, method, rng, **params):
|
2887
3030
|
# make sure that tests catch if sample is 0d array
|
2888
3031
|
if self._overrides('_sample_formula') and not isinstance(rng, qmc.QMCEngine):
|
2889
3032
|
method = self._sample_formula
|
@@ -2891,20 +3034,21 @@ class ContinuousDistribution(_ProbabilityDistribution):
|
|
2891
3034
|
method = self._sample_inverse_transform
|
2892
3035
|
return method
|
2893
3036
|
|
2894
|
-
def _sample_formula(self,
|
3037
|
+
def _sample_formula(self, full_shape, *, rng, **params):
|
2895
3038
|
raise NotImplementedError(self._not_implemented)
|
2896
3039
|
|
2897
|
-
def _sample_inverse_transform(self,
|
3040
|
+
def _sample_inverse_transform(self, full_shape, *, rng, **params):
|
2898
3041
|
if isinstance(rng, qmc.QMCEngine):
|
2899
|
-
uniform = self._qmc_uniform(
|
3042
|
+
uniform = self._qmc_uniform(full_shape, qrng=rng, **params)
|
2900
3043
|
else:
|
2901
3044
|
uniform = rng.random(size=full_shape, dtype=self._dtype)
|
2902
3045
|
return self._icdf_dispatch(uniform, **params)
|
2903
3046
|
|
2904
|
-
def _qmc_uniform(self,
|
3047
|
+
def _qmc_uniform(self, full_shape, *, qrng, **params):
|
2905
3048
|
# Generate QMC uniform sample(s) on unit interval with specified shape;
|
2906
3049
|
# if `sample_shape != ()`, then each slice along axis 0 is independent.
|
2907
3050
|
|
3051
|
+
sample_shape = full_shape[:len(full_shape)-len(self._shape)]
|
2908
3052
|
# Determine the number of independent sequences and the length of each.
|
2909
3053
|
n_low_discrepancy = sample_shape[0] if sample_shape else 1
|
2910
3054
|
n_independent = math.prod(full_shape[1:] if sample_shape else full_shape)
|
@@ -3022,7 +3166,7 @@ class ContinuousDistribution(_ProbabilityDistribution):
|
|
3022
3166
|
moment = self._moment_raw_general(order, **params)
|
3023
3167
|
|
3024
3168
|
if moment is None and 'quadrature' in methods:
|
3025
|
-
moment = self.
|
3169
|
+
moment = self._moment_from_pxf(order, center=self._zero, **params)
|
3026
3170
|
|
3027
3171
|
if moment is None and 'quadrature_icdf' in methods:
|
3028
3172
|
moment = self._moment_integrate_icdf(order, center=self._zero, **params)
|
@@ -3085,7 +3229,7 @@ class ContinuousDistribution(_ProbabilityDistribution):
|
|
3085
3229
|
if moment is None and 'quadrature' in methods:
|
3086
3230
|
mean = self._moment_raw_dispatch(self._one, **params,
|
3087
3231
|
methods=self._moment_methods)
|
3088
|
-
moment = self.
|
3232
|
+
moment = self._moment_from_pxf(order, center=mean, **params)
|
3089
3233
|
|
3090
3234
|
if moment is None and 'quadrature_icdf' in methods:
|
3091
3235
|
mean = self._moment_raw_dispatch(self._one, **params,
|
@@ -3176,10 +3320,10 @@ class ContinuousDistribution(_ProbabilityDistribution):
|
|
3176
3320
|
general_standard_moments = {0: self._one, 1: self._zero, 2: self._one}
|
3177
3321
|
return general_standard_moments.get(order, None)
|
3178
3322
|
|
3179
|
-
def
|
3323
|
+
def _moment_from_pxf(self, order, center, **params):
|
3180
3324
|
def integrand(x, order, center, **params):
|
3181
|
-
|
3182
|
-
return
|
3325
|
+
pxf = self._pxf_dispatch(x, **params)
|
3326
|
+
return pxf*(x-center)**order
|
3183
3327
|
return self._quadrature(integrand, args=(order, center), params=params)
|
3184
3328
|
|
3185
3329
|
def _moment_integrate_icdf(self, order, center, **params):
|
@@ -3215,7 +3359,7 @@ class ContinuousDistribution(_ProbabilityDistribution):
|
|
3215
3359
|
|
3216
3360
|
def _logmoment_quad(self, order, logcenter, **params):
|
3217
3361
|
def logintegrand(x, order, logcenter, **params):
|
3218
|
-
logpdf = self.
|
3362
|
+
logpdf = self._logpxf_dispatch(x, **params)
|
3219
3363
|
return logpdf + order * _logexpxmexpy(np.log(x + 0j), logcenter)
|
3220
3364
|
## if logx == logcenter, `_logexpxmexpy` returns (-inf + 0j)
|
3221
3365
|
## multiplying by order produces (-inf + nan j) - bad
|
@@ -3231,7 +3375,7 @@ class ContinuousDistribution(_ProbabilityDistribution):
|
|
3231
3375
|
|
3232
3376
|
### Convenience
|
3233
3377
|
|
3234
|
-
def plot(self, x='x', y=
|
3378
|
+
def plot(self, x='x', y=None, *, t=None, ax=None):
|
3235
3379
|
r"""Plot a function of the distribution.
|
3236
3380
|
|
3237
3381
|
Convenience function for quick visualization of the distribution
|
@@ -3242,14 +3386,18 @@ class ContinuousDistribution(_ProbabilityDistribution):
|
|
3242
3386
|
x, y : str, optional
|
3243
3387
|
String indicating the quantities to be used as the abscissa and
|
3244
3388
|
ordinate (horizontal and vertical coordinates), respectively.
|
3245
|
-
Defaults are ``'x'`` (the domain of the random variable) and
|
3246
|
-
``'pdf'`` (the probability density function)
|
3247
|
-
'
|
3248
|
-
|
3389
|
+
Defaults are ``'x'`` (the domain of the random variable) and either
|
3390
|
+
``'pdf'`` (the probability density function) (continuous) or
|
3391
|
+
``'pdf'`` (the probability density function) (discrete).
|
3392
|
+
Valid values are:
|
3393
|
+
'x', 'pdf', 'pmf', 'cdf', 'ccdf', 'icdf', 'iccdf', 'logpdf', 'logpmf',
|
3394
|
+
'logcdf', 'logccdf', 'ilogcdf', 'ilogccdf'.
|
3249
3395
|
t : 3-tuple of (str, float, float), optional
|
3250
3396
|
Tuple indicating the limits within which the quantities are plotted.
|
3251
|
-
|
3252
|
-
99.9% of the distribution is to be shown
|
3397
|
+
The default is ``('cdf', 0.0005, 0.9995)`` if the domain is infinite,
|
3398
|
+
indicating that the central 99.9% of the distribution is to be shown;
|
3399
|
+
otherwise, endpoints of the support are used where they are finite.
|
3400
|
+
Valid values are:
|
3253
3401
|
'x', 'cdf', 'ccdf', 'icdf', 'iccdf', 'logcdf', 'logccdf',
|
3254
3402
|
'ilogcdf', 'ilogccdf'.
|
3255
3403
|
ax : `matplotlib.axes`, optional
|
@@ -3307,14 +3455,24 @@ class ContinuousDistribution(_ProbabilityDistribution):
|
|
3307
3455
|
# - when the parameters of the distribution are an array,
|
3308
3456
|
# use the full range of abscissae for all curves
|
3309
3457
|
|
3458
|
+
discrete = isinstance(self, DiscreteDistribution)
|
3310
3459
|
t_is_quantile = {'x', 'icdf', 'iccdf', 'ilogcdf', 'ilogccdf'}
|
3311
3460
|
t_is_probability = {'cdf', 'ccdf', 'logcdf', 'logccdf'}
|
3312
3461
|
valid_t = t_is_quantile.union(t_is_probability)
|
3313
|
-
valid_xy = valid_t.union({'pdf', 'logpdf'})
|
3462
|
+
valid_xy = valid_t.union({'pdf', 'logpdf', 'pmf', 'logpmf'})
|
3463
|
+
y_default = 'pmf' if discrete else 'pdf'
|
3464
|
+
y = y_default if y is None else y
|
3314
3465
|
|
3315
3466
|
ndim = self._ndim
|
3316
3467
|
x_name, y_name = x, y
|
3317
|
-
t_name
|
3468
|
+
t_name = 'cdf' if t is None else t[0]
|
3469
|
+
|
3470
|
+
a, b = self.support()
|
3471
|
+
tliml_default = 0 if np.all(np.isfinite(a)) else 0.0005
|
3472
|
+
tliml = tliml_default if t is None else t[1]
|
3473
|
+
tlimr_default = 1 if np.all(np.isfinite(b)) else 0.9995
|
3474
|
+
tlimr = tlimr_default if t is None else t[2]
|
3475
|
+
tlim = np.asarray([tliml, tlimr])
|
3318
3476
|
tlim = tlim[:, np.newaxis] if ndim else tlim
|
3319
3477
|
|
3320
3478
|
# pdf/logpdf are not valid for `t` because we can't easily invert them
|
@@ -3330,7 +3488,7 @@ class ContinuousDistribution(_ProbabilityDistribution):
|
|
3330
3488
|
|
3331
3489
|
message = (f'Argument `y` of `{self.__class__.__name__}.plot` "'
|
3332
3490
|
f'must be one of {valid_xy}')
|
3333
|
-
if
|
3491
|
+
if y_name not in valid_xy:
|
3334
3492
|
raise ValueError(message)
|
3335
3493
|
|
3336
3494
|
# This could just be a warning
|
@@ -3363,16 +3521,32 @@ class ContinuousDistribution(_ProbabilityDistribution):
|
|
3363
3521
|
raise ValueError(message)
|
3364
3522
|
|
3365
3523
|
# form quantile grid
|
3366
|
-
|
3367
|
-
|
3368
|
-
|
3524
|
+
if discrete and x_name in t_is_quantile:
|
3525
|
+
# should probably aggregate for large ranges
|
3526
|
+
q = np.arange(np.min(qlim[0]), np.max(qlim[1]) + 1)
|
3527
|
+
q = q[:, np.newaxis] if ndim else q
|
3528
|
+
else:
|
3529
|
+
grid = np.linspace(0, 1, 300)
|
3530
|
+
grid = grid[:, np.newaxis] if ndim else grid
|
3531
|
+
q = qlim[0] + (qlim[1] - qlim[0]) * grid
|
3532
|
+
q = np.round(q) if discrete else q
|
3369
3533
|
|
3370
3534
|
# compute requested x and y at quantile grid
|
3371
3535
|
x = q if x_name in t_is_quantile else getattr(self, x_name)(q)
|
3372
3536
|
y = q if y_name in t_is_quantile else getattr(self, y_name)(q)
|
3373
3537
|
|
3374
3538
|
# make plot
|
3375
|
-
|
3539
|
+
x, y = np.broadcast_arrays(x.T, np.atleast_2d(y.T))
|
3540
|
+
for xi, yi in zip(x, y): # plot is vectorized, but bar/step don't seem to be
|
3541
|
+
if discrete and x_name in t_is_quantile and y_name == 'pmf':
|
3542
|
+
# should this just be a step plot, too?
|
3543
|
+
ax.bar(xi, yi, alpha=np.sqrt(1/y.shape[0])) # alpha heuristic
|
3544
|
+
elif discrete and x_name in t_is_quantile:
|
3545
|
+
values = yi
|
3546
|
+
edges = np.concatenate((xi, [xi[-1]+1]))
|
3547
|
+
ax.stairs(values, edges, baseline=None)
|
3548
|
+
else:
|
3549
|
+
ax.plot(xi, yi)
|
3376
3550
|
ax.set_xlabel(f"${x_name}$")
|
3377
3551
|
ax.set_ylabel(f"${y_name}$")
|
3378
3552
|
ax.set_title(str(self))
|
@@ -3422,8 +3596,176 @@ class ContinuousDistribution(_ProbabilityDistribution):
|
|
3422
3596
|
# these methods reasonably efficiently.
|
3423
3597
|
|
3424
3598
|
|
3599
|
+
class ContinuousDistribution(UnivariateDistribution):
|
3600
|
+
def _overrides(self, method_name):
|
3601
|
+
if method_name in {'_logpmf_formula', '_pmf_formula'}:
|
3602
|
+
return True
|
3603
|
+
return super()._overrides(method_name)
|
3604
|
+
|
3605
|
+
def _pmf_formula(self, x, **params):
|
3606
|
+
return np.zeros_like(x)
|
3607
|
+
|
3608
|
+
def _logpmf_formula(self, x, **params):
|
3609
|
+
return np.full_like(x, -np.inf)
|
3610
|
+
|
3611
|
+
def _pxf_dispatch(self, x, *, method=None, **params):
|
3612
|
+
return self._pdf_dispatch(x, method=method, **params)
|
3613
|
+
|
3614
|
+
def _logpxf_dispatch(self, x, *, method=None, **params):
|
3615
|
+
return self._logpdf_dispatch(x, method=method, **params)
|
3616
|
+
|
3617
|
+
def _solve_bounded_continuous(self, func, p, params, xatol=None):
|
3618
|
+
return self._solve_bounded(func, p, params=params, xatol=xatol).x
|
3619
|
+
|
3620
|
+
|
3621
|
+
class DiscreteDistribution(UnivariateDistribution):
|
3622
|
+
def _overrides(self, method_name):
|
3623
|
+
if method_name in {'_logpdf_formula', '_pdf_formula'}:
|
3624
|
+
return True
|
3625
|
+
return super()._overrides(method_name)
|
3626
|
+
|
3627
|
+
def _logpdf_formula(self, x, **params):
|
3628
|
+
if params:
|
3629
|
+
p = next(iter(params.values()))
|
3630
|
+
nan_result = np.isnan(x) | np.isnan(p)
|
3631
|
+
else:
|
3632
|
+
nan_result = np.isnan(x)
|
3633
|
+
return np.where(nan_result, np.nan, np.inf)
|
3634
|
+
|
3635
|
+
def _pdf_formula(self, x, **params):
|
3636
|
+
if params:
|
3637
|
+
p = next(iter(params.values()))
|
3638
|
+
nan_result = np.isnan(x) | np.isnan(p)
|
3639
|
+
else:
|
3640
|
+
nan_result = np.isnan(x)
|
3641
|
+
return np.where(nan_result, np.nan, np.inf)
|
3642
|
+
|
3643
|
+
def _pxf_dispatch(self, x, *, method=None, **params):
|
3644
|
+
return self._pmf_dispatch(x, method=method, **params)
|
3645
|
+
|
3646
|
+
def _logpxf_dispatch(self, x, *, method=None, **params):
|
3647
|
+
return self._logpmf_dispatch(x, method=method, **params)
|
3648
|
+
|
3649
|
+
def _cdf_quadrature(self, x, **params):
|
3650
|
+
return super()._cdf_quadrature(np.floor(x), **params)
|
3651
|
+
|
3652
|
+
def _logcdf_quadrature(self, x, **params):
|
3653
|
+
return super()._logcdf_quadrature(np.floor(x), **params)
|
3654
|
+
|
3655
|
+
def _ccdf_quadrature(self, x, **params):
|
3656
|
+
return super()._ccdf_quadrature(np.floor(x + 1), **params)
|
3657
|
+
|
3658
|
+
def _logccdf_quadrature(self, x, **params):
|
3659
|
+
return super()._logccdf_quadrature(np.floor(x + 1), **params)
|
3660
|
+
|
3661
|
+
def _cdf2(self, x, y, *, method):
|
3662
|
+
raise NotImplementedError(
|
3663
|
+
"Two argument cdf functions are currently only supported for "
|
3664
|
+
"continuous distributions.")
|
3665
|
+
|
3666
|
+
def _ccdf2(self, x, y, *, method):
|
3667
|
+
raise NotImplementedError(
|
3668
|
+
"Two argument cdf functions are currently only supported for "
|
3669
|
+
"continuous distributions.")
|
3670
|
+
|
3671
|
+
def _logcdf2(self, x, y, *, method):
|
3672
|
+
raise NotImplementedError(
|
3673
|
+
"Two argument cdf functions are currently only supported for "
|
3674
|
+
"continuous distributions.")
|
3675
|
+
|
3676
|
+
def _logccdf2(self, x, y, *, method):
|
3677
|
+
raise NotImplementedError(
|
3678
|
+
"Two argument cdf functions are currently only supported for "
|
3679
|
+
"continuous distributions.")
|
3680
|
+
|
3681
|
+
def _solve_bounded_discrete(self, func, p, params, comp):
|
3682
|
+
res = self._solve_bounded(func, p, params=params, xatol=0.9)
|
3683
|
+
x = np.asarray(np.floor(res.xr))
|
3684
|
+
|
3685
|
+
# if _chandrupatla finds exact inverse, the bracket may not have been reduced
|
3686
|
+
# enough for `np.floor(res.x)` to be the appropriate value of `x`.
|
3687
|
+
mask = res.fun == 0
|
3688
|
+
x[mask] = np.floor(res.x[mask])
|
3689
|
+
|
3690
|
+
xmin, xmax = self._support(**params)
|
3691
|
+
p, xmin, xmax = np.broadcast_arrays(p, xmin, xmax)
|
3692
|
+
mask = comp(func(xmin, **params), p)
|
3693
|
+
x[mask] = xmin[mask]
|
3694
|
+
|
3695
|
+
return x
|
3696
|
+
|
3697
|
+
def _base_discrete_inversion(self, p, func, comp, /, **params):
|
3698
|
+
# For discrete distributions, icdf(p) is defined as the minimum n
|
3699
|
+
# such that cdf(n) >= p. iccdf(p) is defined as the minimum n such
|
3700
|
+
# that ccdf(n) <= p, or equivalently as iccdf(p) = icdf(1 - p).
|
3701
|
+
|
3702
|
+
# First try to find where cdf(x) == p for the continuous extension of the
|
3703
|
+
# cdf. res.xl and res.xr will be a bracket for this root. The parameter
|
3704
|
+
# xatol in solve_bounded controls the bracket width. We thus know that
|
3705
|
+
# know cdf(res.xr) >= p, cdf(res.xl) <= p, and |res.xr - res.xl| <= 0.9.
|
3706
|
+
# This means the minimum integer n such that cdf(n) >= p is either floor(x)
|
3707
|
+
# or floor(x) + 1.
|
3708
|
+
x = self._solve_bounded_discrete(func, p, params=params, comp=comp)
|
3709
|
+
# comp should be <= for ccdf, >= for cdf.
|
3710
|
+
f = func(x, **params)
|
3711
|
+
res = np.where(comp(f, p), x, x + 1.0)
|
3712
|
+
# xr is a bracket endpoint, and will usually be a finite value even when
|
3713
|
+
# the computed result should be nan. We need to explicitly handle this
|
3714
|
+
# case.
|
3715
|
+
res[np.isnan(f) | np.isnan(p)] = np.nan
|
3716
|
+
return res[()]
|
3717
|
+
|
3718
|
+
def _icdf_inversion(self, x, **params):
|
3719
|
+
return self._base_discrete_inversion(x, self._cdf_dispatch,
|
3720
|
+
np.greater_equal, **params)
|
3721
|
+
|
3722
|
+
def _ilogcdf_inversion(self, x, **params):
|
3723
|
+
return self._base_discrete_inversion(x, self._logcdf_dispatch,
|
3724
|
+
np.greater_equal, **params)
|
3725
|
+
|
3726
|
+
def _iccdf_inversion(self, x, **params):
|
3727
|
+
return self._base_discrete_inversion(x, self._ccdf_dispatch,
|
3728
|
+
np.less_equal, **params)
|
3729
|
+
|
3730
|
+
def _ilogccdf_inversion(self, x, **params):
|
3731
|
+
return self._base_discrete_inversion(x, self._logccdf_dispatch,
|
3732
|
+
np.less_equal, **params)
|
3733
|
+
|
3734
|
+
def _mode_optimization(self, **params):
|
3735
|
+
# If `x` is the true mode of a unimodal continuous function, we can find
|
3736
|
+
# the mode among integers by rounding in each direction and checking
|
3737
|
+
# which is better. If the difference between `x` and the nearest integer
|
3738
|
+
# is less than `xatol`, the computed value of `x` may end up on the wrong
|
3739
|
+
# side of the nearest integer. Setting `xatol=0.5` guarantees that at most
|
3740
|
+
# three integers need to be checked, the two nearest integers, ``floor(x)``
|
3741
|
+
# and ``round(x)`` and the nearest integer other than these.
|
3742
|
+
x = super()._mode_optimization(xatol=0.5, **params)
|
3743
|
+
low, high = self.support()
|
3744
|
+
xl, xr = np.floor(x), np.ceil(x)
|
3745
|
+
nearest = np.round(x)
|
3746
|
+
# Clip to stay within support. There will be redundant calculation
|
3747
|
+
# when clipping since `xo` will be one of `xl` or `xr`, but let's
|
3748
|
+
# keep the implementation simple for now.
|
3749
|
+
xo = np.clip(nearest + np.copysign(1, nearest - x), low, high)
|
3750
|
+
x = np.stack([xl, xo, xr])
|
3751
|
+
idx = np.argmax(self._pmf_dispatch(x, **params), axis=0)
|
3752
|
+
return np.choose(idx, [xl, xo, xr])
|
3753
|
+
|
3754
|
+
def _logentropy_quadrature(self, **params):
|
3755
|
+
def logintegrand(x, **params):
|
3756
|
+
logpmf = self._logpmf_dispatch(x, **params)
|
3757
|
+
# Entropy summand is -pmf*log(pmf), so log-entropy summand is
|
3758
|
+
# logpmf + log(logpmf) + pi*j. But pmf is always between 0 and 1,
|
3759
|
+
# so logpmf is always negative, and so log(logpmf) = log(-logpmf) + pi*j.
|
3760
|
+
# The two imaginary components "cancel" each other out (which we would
|
3761
|
+
# expect because each term of the entropy summand is positive).
|
3762
|
+
return np.where(np.isfinite(logpmf), logpmf + np.log(-logpmf), -np.inf)
|
3763
|
+
return self._quadrature(logintegrand, params=params, log=True)
|
3764
|
+
|
3765
|
+
|
3425
3766
|
# Special case the names of some new-style distributions in `make_distribution`
|
3426
3767
|
_distribution_names = {
|
3768
|
+
# Continuous
|
3427
3769
|
'argus': 'ARGUS',
|
3428
3770
|
'betaprime': 'BetaPrime',
|
3429
3771
|
'chi2': 'ChiSquared',
|
@@ -3496,41 +3838,127 @@ _distribution_names = {
|
|
3496
3838
|
'weibull_min': 'Weibull',
|
3497
3839
|
'weibull_max': 'ReflectedWeibull',
|
3498
3840
|
'wrapcauchy': 'WrappedCauchyLine',
|
3841
|
+
# Discrete
|
3842
|
+
'betabinom': 'BetaBinomial',
|
3843
|
+
'betanbinom': 'BetaNegativeBinomial',
|
3844
|
+
'dlaplace': 'LaplaceDiscrete',
|
3845
|
+
'geom': 'Geometric',
|
3846
|
+
'hypergeom': 'Hypergeometric',
|
3847
|
+
'logser': 'LogarithmicSeries',
|
3848
|
+
'nbinom': 'NegativeBinomial',
|
3849
|
+
'nchypergeom_fisher': 'NoncentralHypergeometricFisher',
|
3850
|
+
'nchypergeom_wallenius': 'NoncentralHypergeometricWallenius',
|
3851
|
+
'nhypergeom': 'NegativeHypergeometric',
|
3852
|
+
'poisson_binom': 'PoissonBinomial',
|
3853
|
+
'randint': 'UniformDiscrete',
|
3854
|
+
'yulesimon': 'YuleSimon',
|
3855
|
+
'zipf': 'Zeta',
|
3499
3856
|
}
|
3500
3857
|
|
3501
3858
|
|
3502
3859
|
# beta, genextreme, gengamma, t, tukeylambda need work for 1D arrays
|
3503
3860
|
def make_distribution(dist):
|
3504
|
-
"""Generate a `
|
3861
|
+
"""Generate a `UnivariateDistribution` class from a compatible object
|
3862
|
+
|
3863
|
+
The argument may be an instance of `rv_continuous` or an instance of
|
3864
|
+
another class that satisfies the interface described below.
|
3505
3865
|
|
3506
|
-
The returned value is a `ContinuousDistribution` subclass
|
3507
|
-
of `
|
3508
|
-
|
3509
|
-
|
3510
|
-
|
3866
|
+
The returned value is a `ContinuousDistribution` subclass if the input is an
|
3867
|
+
instance of `rv_continuous` or a `DiscreteDistribution` subclass if the input
|
3868
|
+
is an instance of `rv_discrete`. Like any subclass of `UnivariateDistribution`,
|
3869
|
+
it must be instantiated (i.e. by passing all shape parameters as keyword
|
3870
|
+
arguments) before use. Once instantiated, the resulting object will have the
|
3871
|
+
same interface as any other instance of `UnivariateDistribution`; e.g.,
|
3872
|
+
`scipy.stats.Normal`, `scipy.stats.Binomial`.
|
3511
3873
|
|
3512
3874
|
.. note::
|
3513
3875
|
|
3514
3876
|
`make_distribution` does not work perfectly with all instances of
|
3515
|
-
`rv_continuous`. Known failures include `levy_stable
|
3516
|
-
|
3517
|
-
|
3877
|
+
`rv_continuous`. Known failures include `levy_stable`, `vonmises`,
|
3878
|
+
`hypergeom`, 'nchypergeom_fisher', 'nchypergeom_wallenius', and
|
3879
|
+
`poisson_binom`. Some methods of some distributions will not support
|
3880
|
+
array shape parameters.
|
3518
3881
|
|
3519
3882
|
Parameters
|
3520
3883
|
----------
|
3521
3884
|
dist : `rv_continuous`
|
3522
|
-
Instance of `rv_continuous
|
3885
|
+
Instance of `rv_continuous`, `rv_discrete`, or an instance of any class with
|
3886
|
+
the following attributes:
|
3887
|
+
|
3888
|
+
__make_distribution_version__ : str
|
3889
|
+
A string containing the version number of SciPy in which this interface
|
3890
|
+
is defined. The preferred interface may change in future SciPy versions,
|
3891
|
+
in which case support for an old interface version may be deprecated
|
3892
|
+
and eventually removed.
|
3893
|
+
parameters : dict or tuple
|
3894
|
+
If a dictionary, each key is the name of a parameter,
|
3895
|
+
and the corresponding value is either a dictionary or tuple.
|
3896
|
+
If the value is a dictionary, it may have the following items, with default
|
3897
|
+
values used for entries which aren't present.
|
3898
|
+
|
3899
|
+
endpoints : tuple, default: (-inf, inf)
|
3900
|
+
A tuple defining the lower and upper endpoints of the domain of the
|
3901
|
+
parameter; allowable values are floats, the name (string) of another
|
3902
|
+
parameter, or a callable taking parameters as keyword only
|
3903
|
+
arguments and returning the numerical value of an endpoint for
|
3904
|
+
given parameter values.
|
3905
|
+
|
3906
|
+
inclusive : tuple of bool, default: (False, False)
|
3907
|
+
A tuple specifying whether the endpoints are included within the domain
|
3908
|
+
of the parameter.
|
3909
|
+
|
3910
|
+
typical : tuple, default: ``endpoints``
|
3911
|
+
Defining endpoints of a typical range of values of a parameter. Can be
|
3912
|
+
used for sampling parameter values for testing. Behaves like the
|
3913
|
+
``endpoints`` tuple above, and should define a subinterval of the
|
3914
|
+
domain given by ``endpoints``.
|
3915
|
+
|
3916
|
+
A tuple value ``(a, b)`` associated to a key in the ``parameters``
|
3917
|
+
dictionary is equivalent to ``{endpoints: (a, b)}``.
|
3918
|
+
|
3919
|
+
Custom distributions with multiple parameterizations can be defined by
|
3920
|
+
having the ``parameters`` attribute be a tuple of dictionaries with
|
3921
|
+
the structure described above. In this case, ``dist``\'s class must also
|
3922
|
+
define a method ``process_parameters`` to map between the different
|
3923
|
+
parameterizations. It must take all parameters from all parameterizations
|
3924
|
+
as optional keyword arguments and return a dictionary mapping parameters to
|
3925
|
+
values, filling in values from other parameterizations using values from
|
3926
|
+
the supplied parameterization. See example.
|
3927
|
+
|
3928
|
+
support : dict or tuple
|
3929
|
+
A dictionary describing the support of the distribution or a tuple
|
3930
|
+
describing the endpoints of the support. This behaves identically to
|
3931
|
+
the values of the parameters dict described above, except that the key
|
3932
|
+
``typical`` is ignored.
|
3933
|
+
|
3934
|
+
The class **must** also define a ``pdf`` method and **may** define methods
|
3935
|
+
``logentropy``, ``entropy``, ``median``, ``mode``, ``logpdf``,
|
3936
|
+
``logcdf``, ``cdf``, ``logccdf``, ``ccdf``,
|
3937
|
+
``ilogcdf``, ``icdf``, ``ilogccdf``, ``iccdf``,
|
3938
|
+
``moment``, and ``sample``.
|
3939
|
+
If defined, these methods must accept the parameters of the distribution as
|
3940
|
+
keyword arguments and also accept any positional-only arguments accepted by
|
3941
|
+
the corresponding method of `ContinuousDistribution`.
|
3942
|
+
When multiple parameterizations are defined, these methods must accept
|
3943
|
+
all parameters from all parameterizations. The ``moment`` method
|
3944
|
+
must accept the ``order`` and ``kind`` arguments by position or keyword, but
|
3945
|
+
may return ``None`` if a formula is not available for the arguments; in this
|
3946
|
+
case, the infrastructure will fall back to a default implementation. The
|
3947
|
+
``sample`` method must accept ``shape`` by position or keyword, but contrary
|
3948
|
+
to the public method of the same name, the argument it receives will be the
|
3949
|
+
*full* shape of the output array - that is, the shape passed to the public
|
3950
|
+
method prepended to the broadcasted shape of random variable parameters.
|
3523
3951
|
|
3524
3952
|
Returns
|
3525
3953
|
-------
|
3526
|
-
CustomDistribution : `
|
3527
|
-
A subclass of `
|
3954
|
+
CustomDistribution : `UnivariateDistribution`
|
3955
|
+
A subclass of `UnivariateDistribution` corresponding with `dist`. The
|
3528
3956
|
initializer requires all shape parameters to be passed as keyword arguments
|
3529
|
-
(using the same names as the instance of `rv_continuous`).
|
3957
|
+
(using the same names as the instance of `rv_continuous`/`rv_discrete`).
|
3530
3958
|
|
3531
3959
|
Notes
|
3532
3960
|
-----
|
3533
|
-
The documentation of `
|
3961
|
+
The documentation of `UnivariateDistribution` is not rendered. See below for
|
3534
3962
|
an example of how to instantiate the class (i.e. pass all shape parameters of
|
3535
3963
|
`dist` to the initializer as keyword arguments). Documentation of all methods
|
3536
3964
|
is identical to that of `scipy.stats.Normal`. Use ``help`` on the returned
|
@@ -3541,8 +3969,12 @@ def make_distribution(dist):
|
|
3541
3969
|
>>> import numpy as np
|
3542
3970
|
>>> import matplotlib.pyplot as plt
|
3543
3971
|
>>> from scipy import stats
|
3544
|
-
>>>
|
3545
|
-
|
3972
|
+
>>> from scipy import special
|
3973
|
+
|
3974
|
+
Create a `ContinuousDistribution` from `scipy.stats.loguniform`.
|
3975
|
+
|
3976
|
+
>>> LogUniform = stats.make_distribution(stats.loguniform)
|
3977
|
+
>>> X = LogUniform(a=1.0, b=3.0)
|
3546
3978
|
>>> np.isclose((X + 0.25).median(), stats.loguniform.ppf(0.5, 1, 3, loc=0.25))
|
3547
3979
|
np.True_
|
3548
3980
|
>>> X.plot()
|
@@ -3551,30 +3983,160 @@ def make_distribution(dist):
|
|
3551
3983
|
>>> plt.legend(('pdf', 'histogram'))
|
3552
3984
|
>>> plt.show()
|
3553
3985
|
|
3986
|
+
Create a custom distribution.
|
3987
|
+
|
3988
|
+
>>> class MyLogUniform:
|
3989
|
+
... @property
|
3990
|
+
... def __make_distribution_version__(self):
|
3991
|
+
... return "1.16.0"
|
3992
|
+
...
|
3993
|
+
... @property
|
3994
|
+
... def parameters(self):
|
3995
|
+
... return {'a': {'endpoints': (0, np.inf),
|
3996
|
+
... 'inclusive': (False, False)},
|
3997
|
+
... 'b': {'endpoints': ('a', np.inf),
|
3998
|
+
... 'inclusive': (False, False)}}
|
3999
|
+
...
|
4000
|
+
... @property
|
4001
|
+
... def support(self):
|
4002
|
+
... return {'endpoints': ('a', 'b'), 'inclusive': (True, True)}
|
4003
|
+
...
|
4004
|
+
... def pdf(self, x, a, b):
|
4005
|
+
... return 1 / (x * (np.log(b)- np.log(a)))
|
4006
|
+
>>>
|
4007
|
+
>>> MyLogUniform = stats.make_distribution(MyLogUniform())
|
4008
|
+
>>> Y = MyLogUniform(a=1.0, b=3.0)
|
4009
|
+
>>> np.isclose(Y.cdf(2.), X.cdf(2.))
|
4010
|
+
np.True_
|
4011
|
+
|
4012
|
+
Create a custom distribution with variable support.
|
4013
|
+
|
4014
|
+
>>> class MyUniformCube:
|
4015
|
+
... @property
|
4016
|
+
... def __make_distribution_version__(self):
|
4017
|
+
... return "1.16.0"
|
4018
|
+
...
|
4019
|
+
... @property
|
4020
|
+
... def parameters(self):
|
4021
|
+
... return {"a": (-np.inf, np.inf),
|
4022
|
+
... "b": {'endpoints':('a', np.inf), 'inclusive':(True, False)}}
|
4023
|
+
...
|
4024
|
+
... @property
|
4025
|
+
... def support(self):
|
4026
|
+
... def left(*, a, b):
|
4027
|
+
... return a**3
|
4028
|
+
...
|
4029
|
+
... def right(*, a, b):
|
4030
|
+
... return b**3
|
4031
|
+
... return (left, right)
|
4032
|
+
...
|
4033
|
+
... def pdf(self, x, *, a, b):
|
4034
|
+
... return 1 / (3*(b - a)*np.cbrt(x)**2)
|
4035
|
+
...
|
4036
|
+
... def cdf(self, x, *, a, b):
|
4037
|
+
... return (np.cbrt(x) - a) / (b - a)
|
4038
|
+
>>>
|
4039
|
+
>>> MyUniformCube = stats.make_distribution(MyUniformCube())
|
4040
|
+
>>> X = MyUniformCube(a=-2, b=2)
|
4041
|
+
>>> Y = stats.Uniform(a=-2, b=2)**3
|
4042
|
+
>>> X.support()
|
4043
|
+
(-8.0, 8.0)
|
4044
|
+
>>> np.isclose(X.cdf(2.1), Y.cdf(2.1))
|
4045
|
+
np.True_
|
4046
|
+
|
4047
|
+
Create a custom distribution with multiple parameterizations. Here we create a
|
4048
|
+
custom version of the beta distribution that has an alternative parameterization
|
4049
|
+
in terms of the mean ``mu`` and a dispersion parameter ``nu``.
|
4050
|
+
|
4051
|
+
>>> class MyBeta:
|
4052
|
+
... @property
|
4053
|
+
... def __make_distribution_version__(self):
|
4054
|
+
... return "1.16.0"
|
4055
|
+
...
|
4056
|
+
... @property
|
4057
|
+
... def parameters(self):
|
4058
|
+
... return ({"a": (0, np.inf), "b": (0, np.inf)},
|
4059
|
+
... {"mu": (0, 1), "nu": (0, np.inf)})
|
4060
|
+
...
|
4061
|
+
... def process_parameters(self, a=None, b=None, mu=None, nu=None):
|
4062
|
+
... if a is not None and b is not None:
|
4063
|
+
... nu = a + b
|
4064
|
+
... mu = a / nu
|
4065
|
+
... else:
|
4066
|
+
... a = mu * nu
|
4067
|
+
... b = nu - a
|
4068
|
+
... return dict(a=a, b=b, mu=mu, nu=nu)
|
4069
|
+
...
|
4070
|
+
... @property
|
4071
|
+
... def support(self):
|
4072
|
+
... return {'endpoints': (0, 1)}
|
4073
|
+
...
|
4074
|
+
... def pdf(self, x, a, b, mu, nu):
|
4075
|
+
... return special._ufuncs._beta_pdf(x, a, b)
|
4076
|
+
...
|
4077
|
+
... def cdf(self, x, a, b, mu, nu):
|
4078
|
+
... return special.betainc(a, b, x)
|
4079
|
+
>>>
|
4080
|
+
>>> MyBeta = stats.make_distribution(MyBeta())
|
4081
|
+
>>> X = MyBeta(a=2.0, b=2.0)
|
4082
|
+
>>> Y = MyBeta(mu=0.5, nu=4.0)
|
4083
|
+
>>> np.isclose(X.pdf(0.3), Y.pdf(0.3))
|
4084
|
+
np.True_
|
4085
|
+
|
3554
4086
|
"""
|
3555
|
-
if dist in {stats.levy_stable, stats.vonmises
|
4087
|
+
if dist in {stats.levy_stable, stats.vonmises, stats.hypergeom,
|
4088
|
+
stats.nchypergeom_fisher, stats.nchypergeom_wallenius,
|
4089
|
+
stats.poisson_binom}:
|
3556
4090
|
raise NotImplementedError(f"`{dist.name}` is not supported.")
|
3557
4091
|
|
3558
|
-
if
|
3559
|
-
|
4092
|
+
if isinstance(dist, stats.rv_continuous | stats.rv_discrete):
|
4093
|
+
return _make_distribution_rv_generic(dist)
|
4094
|
+
elif getattr(dist, "__make_distribution_version__", "0.0.0") >= "1.16.0":
|
4095
|
+
return _make_distribution_custom(dist)
|
4096
|
+
else:
|
4097
|
+
message = ("The argument must be an instance of `rv_continuous`, "
|
4098
|
+
"`rv_discrete`, or an instance of a class with attribute "
|
4099
|
+
"`__make_distribution_version__ >= 1.16`.")
|
3560
4100
|
raise ValueError(message)
|
3561
4101
|
|
4102
|
+
def _make_distribution_rv_generic(dist):
|
3562
4103
|
parameters = []
|
3563
4104
|
names = []
|
3564
4105
|
support = getattr(dist, '_support', (dist.a, dist.b))
|
3565
4106
|
for shape_info in dist._shape_info():
|
3566
|
-
domain =
|
4107
|
+
domain = _RealInterval(endpoints=shape_info.endpoints,
|
3567
4108
|
inclusive=shape_info.inclusive)
|
3568
4109
|
param = _RealParameter(shape_info.name, domain=domain)
|
3569
4110
|
parameters.append(param)
|
3570
4111
|
names.append(shape_info.name)
|
3571
4112
|
|
3572
|
-
_x_support = _RealDomain(endpoints=support, inclusive=(True, True))
|
3573
|
-
_x_param = _RealParameter('x', domain=_x_support, typical=(-1, 1))
|
3574
|
-
|
3575
4113
|
repr_str = _distribution_names.get(dist.name, dist.name.capitalize())
|
4114
|
+
if isinstance(dist, stats.rv_continuous):
|
4115
|
+
old_class, new_class = stats.rv_continuous, ContinuousDistribution
|
4116
|
+
else:
|
4117
|
+
old_class, new_class = stats.rv_discrete, DiscreteDistribution
|
3576
4118
|
|
3577
|
-
|
4119
|
+
def _overrides(method_name):
|
4120
|
+
return (getattr(dist.__class__, method_name, None)
|
4121
|
+
is not getattr(old_class, method_name, None))
|
4122
|
+
|
4123
|
+
if _overrides("_get_support"):
|
4124
|
+
def left(**parameter_values):
|
4125
|
+
a, _ = dist._get_support(**parameter_values)
|
4126
|
+
return np.asarray(a)[()]
|
4127
|
+
|
4128
|
+
def right(**parameter_values):
|
4129
|
+
_, b = dist._get_support(**parameter_values)
|
4130
|
+
return np.asarray(b)[()]
|
4131
|
+
|
4132
|
+
endpoints = (left, right)
|
4133
|
+
else:
|
4134
|
+
endpoints = support
|
4135
|
+
|
4136
|
+
_x_support = _RealInterval(endpoints=endpoints, inclusive=(True, True))
|
4137
|
+
_x_param = _RealParameter('x', domain=_x_support, typical=(-1, 1))
|
4138
|
+
|
4139
|
+
class CustomDistribution(new_class):
|
3578
4140
|
_parameterizations = ([_Parameterization(*parameters)] if parameters
|
3579
4141
|
else [])
|
3580
4142
|
_variable = _x_param
|
@@ -3587,14 +4149,7 @@ def make_distribution(dist):
|
|
3587
4149
|
s = super().__str__()
|
3588
4150
|
return s.replace('CustomDistribution', repr_str)
|
3589
4151
|
|
3590
|
-
|
3591
|
-
# distribution's `_support` to ensure that `_support` takes care
|
3592
|
-
# of any required broadcasting, etc.
|
3593
|
-
def get_numerical_endpoints(parameter_values):
|
3594
|
-
a, b = dist._get_support(**parameter_values)
|
3595
|
-
return np.asarray(a)[()], np.asarray(b)[()]
|
3596
|
-
|
3597
|
-
def _sample_formula(self, _, full_shape=(), *, rng=None, **kwargs):
|
4152
|
+
def _sample_formula(self, full_shape=(), *, rng=None, **kwargs):
|
3598
4153
|
return dist._rvs(size=full_shape, random_state=rng, **kwargs)
|
3599
4154
|
|
3600
4155
|
def _moment_raw_formula(self, order, **kwargs):
|
@@ -3625,6 +4180,8 @@ def make_distribution(dist):
|
|
3625
4180
|
|
3626
4181
|
methods = {'_logpdf': '_logpdf_formula',
|
3627
4182
|
'_pdf': '_pdf_formula',
|
4183
|
+
'_logpmf': '_logpmf_formula',
|
4184
|
+
'_pmf': '_pmf_formula',
|
3628
4185
|
'_logcdf': '_logcdf_formula',
|
3629
4186
|
'_cdf': '_cdf_formula',
|
3630
4187
|
'_logsf': '_logccdf_formula',
|
@@ -3642,19 +4199,11 @@ def make_distribution(dist):
|
|
3642
4199
|
continue
|
3643
4200
|
# If method of old distribution overrides generic implementation...
|
3644
4201
|
method = getattr(dist.__class__, old_method, None)
|
3645
|
-
super_method = getattr(
|
4202
|
+
super_method = getattr(old_class, old_method, None)
|
3646
4203
|
if method is not super_method:
|
3647
4204
|
# Make it an attribute of the new object with the new name
|
3648
4205
|
setattr(CustomDistribution, new_method, getattr(dist, old_method))
|
3649
4206
|
|
3650
|
-
def _overrides(method_name):
|
3651
|
-
return (getattr(dist.__class__, method_name, None)
|
3652
|
-
is not getattr(stats.rv_continuous, method_name, None))
|
3653
|
-
|
3654
|
-
if _overrides('_get_support'):
|
3655
|
-
domain = CustomDistribution._variable.domain
|
3656
|
-
domain.get_numerical_endpoints = get_numerical_endpoints
|
3657
|
-
|
3658
4207
|
if _overrides('_munp'):
|
3659
4208
|
CustomDistribution._moment_raw_formula = _moment_raw_formula
|
3660
4209
|
|
@@ -3670,7 +4219,7 @@ def make_distribution(dist):
|
|
3670
4219
|
support_etc = _combine_docs(CustomDistribution, include_examples=False).lstrip()
|
3671
4220
|
docs = [
|
3672
4221
|
f"This class represents `scipy.stats.{dist.name}` as a subclass of "
|
3673
|
-
"`
|
4222
|
+
f"`{new_class}`.",
|
3674
4223
|
f"The `repr`/`str` of class instances is `{repr_str}`.",
|
3675
4224
|
f"The PDF of the distribution is defined {support_etc}"
|
3676
4225
|
]
|
@@ -3679,6 +4228,93 @@ def make_distribution(dist):
|
|
3679
4228
|
return CustomDistribution
|
3680
4229
|
|
3681
4230
|
|
4231
|
+
def _get_domain_info(info):
|
4232
|
+
domain_info = {"endpoints": info} if isinstance(info, tuple) else info
|
4233
|
+
typical = domain_info.pop("typical", None)
|
4234
|
+
return domain_info, typical
|
4235
|
+
|
4236
|
+
|
4237
|
+
def _make_distribution_custom(dist):
|
4238
|
+
dist_parameters = (
|
4239
|
+
dist.parameters if isinstance(dist.parameters, tuple) else (dist.parameters, )
|
4240
|
+
)
|
4241
|
+
parameterizations = []
|
4242
|
+
for parameterization in dist_parameters:
|
4243
|
+
# The attribute name ``parameters`` appears reasonable from a user facing
|
4244
|
+
# perspective, but there is a little tension here with the internal. It's
|
4245
|
+
# important to keep in mind that the ``parameters`` attribute in a
|
4246
|
+
# user-created custom distribution specifies ``_parameterizations`` within
|
4247
|
+
# the infrastructure.
|
4248
|
+
parameters = []
|
4249
|
+
|
4250
|
+
for name, info in parameterization.items():
|
4251
|
+
domain_info, typical = _get_domain_info(info)
|
4252
|
+
domain = _RealInterval(**domain_info)
|
4253
|
+
param = _RealParameter(name, domain=domain, typical=typical)
|
4254
|
+
parameters.append(param)
|
4255
|
+
parameterizations.append(_Parameterization(*parameters) if parameters else [])
|
4256
|
+
|
4257
|
+
domain_info, _ = _get_domain_info(dist.support)
|
4258
|
+
_x_support = _RealInterval(**domain_info)
|
4259
|
+
_x_param = _RealParameter('x', domain=_x_support)
|
4260
|
+
repr_str = dist.__class__.__name__
|
4261
|
+
|
4262
|
+
class CustomDistribution(ContinuousDistribution):
|
4263
|
+
_parameterizations = parameterizations
|
4264
|
+
_variable = _x_param
|
4265
|
+
|
4266
|
+
def __repr__(self):
|
4267
|
+
s = super().__repr__()
|
4268
|
+
return s.replace('CustomDistribution', repr_str)
|
4269
|
+
|
4270
|
+
def __str__(self):
|
4271
|
+
s = super().__str__()
|
4272
|
+
return s.replace('CustomDistribution', repr_str)
|
4273
|
+
|
4274
|
+
methods = {'sample', 'logentropy', 'entropy',
|
4275
|
+
'median', 'mode', 'logpdf', 'pdf',
|
4276
|
+
'logcdf2', 'logcdf', 'cdf2', 'cdf',
|
4277
|
+
'logccdf2', 'logccdf', 'ccdf2', 'ccdf',
|
4278
|
+
'ilogcdf', 'icdf', 'ilogccdf', 'iccdf'}
|
4279
|
+
|
4280
|
+
for method in methods:
|
4281
|
+
if hasattr(dist, method):
|
4282
|
+
# Make it an attribute of the new object with the new name
|
4283
|
+
new_method = f"_{method}_formula"
|
4284
|
+
setattr(CustomDistribution, new_method, getattr(dist, method))
|
4285
|
+
|
4286
|
+
if hasattr(dist, 'moment'):
|
4287
|
+
def _moment_raw_formula(self, order, **kwargs):
|
4288
|
+
return dist.moment(order, kind='raw', **kwargs)
|
4289
|
+
|
4290
|
+
def _moment_central_formula(self, order, **kwargs):
|
4291
|
+
return dist.moment(order, kind='central', **kwargs)
|
4292
|
+
|
4293
|
+
def _moment_standardized_formula(self, order, **kwargs):
|
4294
|
+
return dist.moment(order, kind='standardized', **kwargs)
|
4295
|
+
|
4296
|
+
CustomDistribution._moment_raw_formula = _moment_raw_formula
|
4297
|
+
CustomDistribution._moment_central_formula = _moment_central_formula
|
4298
|
+
CustomDistribution._moment_standardized_formula = _moment_standardized_formula
|
4299
|
+
|
4300
|
+
if hasattr(dist, 'process_parameters'):
|
4301
|
+
setattr(
|
4302
|
+
CustomDistribution,
|
4303
|
+
"_process_parameters",
|
4304
|
+
getattr(dist, "process_parameters")
|
4305
|
+
)
|
4306
|
+
|
4307
|
+
support_etc = _combine_docs(CustomDistribution, include_examples=False).lstrip()
|
4308
|
+
docs = [
|
4309
|
+
f"This class represents `{repr_str}` as a subclass of "
|
4310
|
+
"`ContinuousDistribution`.",
|
4311
|
+
f"The PDF of the distribution is defined {support_etc}"
|
4312
|
+
]
|
4313
|
+
CustomDistribution.__doc__ = ("\n".join(docs))
|
4314
|
+
|
4315
|
+
return CustomDistribution
|
4316
|
+
|
4317
|
+
|
3682
4318
|
# Rough sketch of how we might shift/scale distributions. The purpose of
|
3683
4319
|
# making it a separate class is for
|
3684
4320
|
# a) simplicity of the ContinuousDistribution class and
|
@@ -3750,6 +4386,9 @@ def _shift_scale_inverse_function(func):
|
|
3750
4386
|
|
3751
4387
|
class TransformedDistribution(ContinuousDistribution):
|
3752
4388
|
def __init__(self, X, /, *args, **kwargs):
|
4389
|
+
if not isinstance(X, ContinuousDistribution):
|
4390
|
+
message = "Transformations are currently only supported for continuous RVs."
|
4391
|
+
raise NotImplementedError(message)
|
3753
4392
|
self._copy_parameterization()
|
3754
4393
|
self._variable = X._variable
|
3755
4394
|
self._dist = X
|
@@ -3805,11 +4444,11 @@ class TruncatedDistribution(TransformedDistribution):
|
|
3805
4444
|
# - if the mode of `_dist` is within the support, it's still the mode
|
3806
4445
|
# - rejection sampling might be more efficient than inverse transform
|
3807
4446
|
|
3808
|
-
_lb_domain =
|
4447
|
+
_lb_domain = _RealInterval(endpoints=(-inf, 'ub'), inclusive=(True, False))
|
3809
4448
|
_lb_param = _RealParameter('lb', symbol=r'b_l',
|
3810
4449
|
domain=_lb_domain, typical=(0.1, 0.2))
|
3811
4450
|
|
3812
|
-
_ub_domain =
|
4451
|
+
_ub_domain = _RealInterval(endpoints=('lb', inf), inclusive=(False, True))
|
3813
4452
|
_ub_param = _RealParameter('ub', symbol=r'b_u',
|
3814
4453
|
domain=_ub_domain, typical=(0.8, 0.9))
|
3815
4454
|
|
@@ -3940,7 +4579,7 @@ def truncate(X, lb=-np.inf, ub=np.inf):
|
|
3940
4579
|
Furthermore, `truncate` can be applied to any random variable:
|
3941
4580
|
|
3942
4581
|
>>> Rayleigh = stats.make_distribution(stats.rayleigh)
|
3943
|
-
>>> W = stats.truncate(Rayleigh(), lb=0, ub=3)
|
4582
|
+
>>> W = stats.truncate(Rayleigh(), lb=0.5, ub=3)
|
3944
4583
|
>>> W.plot()
|
3945
4584
|
>>> plt.show()
|
3946
4585
|
|
@@ -3951,11 +4590,11 @@ def truncate(X, lb=-np.inf, ub=np.inf):
|
|
3951
4590
|
class ShiftedScaledDistribution(TransformedDistribution):
|
3952
4591
|
"""Distribution with a standard shift/scale transformation."""
|
3953
4592
|
# Unclear whether infinite loc/scale will work reasonably in all cases
|
3954
|
-
_loc_domain =
|
4593
|
+
_loc_domain = _RealInterval(endpoints=(-inf, inf), inclusive=(True, True))
|
3955
4594
|
_loc_param = _RealParameter('loc', symbol=r'\mu',
|
3956
4595
|
domain=_loc_domain, typical=(1, 2))
|
3957
4596
|
|
3958
|
-
_scale_domain =
|
4597
|
+
_scale_domain = _RealInterval(endpoints=(-inf, inf), inclusive=(True, True))
|
3959
4598
|
_scale_param = _RealParameter('scale', symbol=r'\sigma',
|
3960
4599
|
domain=_scale_domain, typical=(0.1, 10))
|
3961
4600
|
|
@@ -4047,6 +4686,26 @@ class ShiftedScaledDistribution(TransformedDistribution):
|
|
4047
4686
|
pdf = self._dist._pdf_dispatch(x, *args, **params)
|
4048
4687
|
return pdf / np.abs(scale)
|
4049
4688
|
|
4689
|
+
def _logpmf_dispatch(self, x, *args, loc, scale, sign, **params):
|
4690
|
+
x = self._transform(x, loc, scale)
|
4691
|
+
logpmf = self._dist._logpmf_dispatch(x, *args, **params)
|
4692
|
+
return logpmf - np.log(np.abs(scale))
|
4693
|
+
|
4694
|
+
def _pmf_dispatch(self, x, *args, loc, scale, sign, **params):
|
4695
|
+
x = self._transform(x, loc, scale)
|
4696
|
+
pmf = self._dist._pmf_dispatch(x, *args, **params)
|
4697
|
+
return pmf / np.abs(scale)
|
4698
|
+
|
4699
|
+
def _logpxf_dispatch(self, x, *args, loc, scale, sign, **params):
|
4700
|
+
x = self._transform(x, loc, scale)
|
4701
|
+
logpxf = self._dist._logpxf_dispatch(x, *args, **params)
|
4702
|
+
return logpxf - np.log(np.abs(scale))
|
4703
|
+
|
4704
|
+
def _pxf_dispatch(self, x, *args, loc, scale, sign, **params):
|
4705
|
+
x = self._transform(x, loc, scale)
|
4706
|
+
pxf = self._dist._pxf_dispatch(x, *args, **params)
|
4707
|
+
return pxf / np.abs(scale)
|
4708
|
+
|
4050
4709
|
# Sorry about the magic. This is just a draft to show the behavior.
|
4051
4710
|
@_shift_scale_distribution_function
|
4052
4711
|
def _logcdf_dispatch(self, x, *, method=None, **params):
|
@@ -4124,10 +4783,9 @@ class ShiftedScaledDistribution(TransformedDistribution):
|
|
4124
4783
|
return self._moment_transform_center(
|
4125
4784
|
order, raw_moments, loc, self._zero)
|
4126
4785
|
|
4127
|
-
def _sample_dispatch(self,
|
4786
|
+
def _sample_dispatch(self, full_shape, *,
|
4128
4787
|
rng, loc, scale, sign, method, **params):
|
4129
|
-
rvs = self._dist._sample_dispatch(
|
4130
|
-
sample_shape, full_shape, method=method, rng=rng, **params)
|
4788
|
+
rvs = self._dist._sample_dispatch(full_shape, method=method, rng=rng, **params)
|
4131
4789
|
return self._itransform(rvs, loc=loc, scale=scale, sign=sign, **params)
|
4132
4790
|
|
4133
4791
|
def __add__(self, loc):
|
@@ -4213,12 +4871,12 @@ class OrderStatisticDistribution(TransformedDistribution):
|
|
4213
4871
|
|
4214
4872
|
"""
|
4215
4873
|
|
4216
|
-
# These can be restricted to
|
4874
|
+
# These can be restricted to _IntegerInterval/_IntegerParameter in a separate
|
4217
4875
|
# PR if desired.
|
4218
|
-
_r_domain =
|
4876
|
+
_r_domain = _RealInterval(endpoints=(1, 'n'), inclusive=(True, True))
|
4219
4877
|
_r_param = _RealParameter('r', domain=_r_domain, typical=(1, 2))
|
4220
4878
|
|
4221
|
-
_n_domain =
|
4879
|
+
_n_domain = _RealInterval(endpoints=(1, np.inf), inclusive=(True, True))
|
4222
4880
|
_n_param = _RealParameter('n', domain=_n_domain, typical=(1, 4))
|
4223
4881
|
|
4224
4882
|
_r_domain.define_parameters(_n_param)
|
@@ -4617,6 +5275,14 @@ class Mixture(_ProbabilityDistribution):
|
|
4617
5275
|
self._raise_if_method(method)
|
4618
5276
|
return self._logsum('logpdf', x)
|
4619
5277
|
|
5278
|
+
def pmf(self, x, /, *, method=None):
|
5279
|
+
self._raise_if_method(method)
|
5280
|
+
return self._sum('pmf', x)
|
5281
|
+
|
5282
|
+
def logpmf(self, x, /, *, method=None):
|
5283
|
+
self._raise_if_method(method)
|
5284
|
+
return self._logsum('logpmf', x)
|
5285
|
+
|
4620
5286
|
def cdf(self, x, y=None, /, *, method=None):
|
4621
5287
|
self._raise_if_method(method)
|
4622
5288
|
args = (x,) if y is None else (x, y)
|
@@ -4814,10 +5480,8 @@ class MonotonicTransformedDistribution(TransformedDistribution):
|
|
4814
5480
|
def _iccdf_dispatch(self, p, *args, **params):
|
4815
5481
|
return self._g(self._icxdf(p, *args, **params))
|
4816
5482
|
|
4817
|
-
def _sample_dispatch(self,
|
4818
|
-
|
4819
|
-
rvs = self._dist._sample_dispatch(
|
4820
|
-
sample_shape, full_shape, method=method, rng=rng, **params)
|
5483
|
+
def _sample_dispatch(self, full_shape, *, method, rng, **params):
|
5484
|
+
rvs = self._dist._sample_dispatch(full_shape, method=method, rng=rng, **params)
|
4821
5485
|
return self._g(rvs)
|
4822
5486
|
|
4823
5487
|
|
@@ -4904,7 +5568,7 @@ class FoldedDistribution(TransformedDistribution):
|
|
4904
5568
|
a, b = self._dist._support(**params)
|
4905
5569
|
xl = np.maximum(-x, a)
|
4906
5570
|
xr = np.minimum(x, b)
|
4907
|
-
return self._dist._logccdf2_dispatch(xl, xr, *args, method=method,
|
5571
|
+
return self._dist._logccdf2_dispatch(xl, xr, *args, method=method,
|
4908
5572
|
**params).real
|
4909
5573
|
|
4910
5574
|
def _ccdf_dispatch(self, x, *args, method=None, **params):
|
@@ -4914,10 +5578,8 @@ class FoldedDistribution(TransformedDistribution):
|
|
4914
5578
|
xr = np.minimum(x, b)
|
4915
5579
|
return self._dist._ccdf2_dispatch(xl, xr, *args, method=method, **params)
|
4916
5580
|
|
4917
|
-
def _sample_dispatch(self,
|
4918
|
-
|
4919
|
-
rvs = self._dist._sample_dispatch(
|
4920
|
-
sample_shape, full_shape, method=method, rng=rng, **params)
|
5581
|
+
def _sample_dispatch(self, full_shape, *, method, rng, **params):
|
5582
|
+
rvs = self._dist._sample_dispatch(full_shape, method=method, rng=rng, **params)
|
4921
5583
|
return np.abs(rvs)
|
4922
5584
|
|
4923
5585
|
def __repr__(self):
|